hooks.md +85 −15
9- Send the conversation to a custom logging/analytics engine9- Send the conversation to a custom logging/analytics engine
10- Scan your team's prompts to block accidentally pasting API keys10- Scan your team's prompts to block accidentally pasting API keys
11- Summarize conversations to create persistent memories automatically11- Summarize conversations to create persistent memories automatically
1212- Run a custom validator when a conversation turn stops, enforcing standards- Run a custom validation check when a conversation turn stops, enforcing standards
13- Customize prompting when in a certain directory13- Customize prompting when in a certain directory
14 14
15Hooks are behind a feature flag in `config.toml`:15Hooks are behind a feature flag in `config.toml`:
23 23
24- Matching hooks from multiple files all run.24- Matching hooks from multiple files all run.
25- Multiple matching command hooks for the same event are launched concurrently,25- Multiple matching command hooks for the same event are launched concurrently,
2626 so one hook cannot prevent another matching hook from starting. so one hook can’t prevent another matching hook from starting.
2727- `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, and `Stop` run at turn- `PreToolUse`, `PermissionRequest`, `PostToolUse`, `UserPromptSubmit`, and
2828 scope. `Stop` run at turn scope.
29- Hooks are currently disabled on Windows.29- Hooks are currently disabled on Windows.
30 30
31## Where Codex looks for hooks31## Where Codex looks for hooks
38- `<repo>/.codex/hooks.json`38- `<repo>/.codex/hooks.json`
39 39
40If more than one `hooks.json` file exists, Codex loads all matching hooks.40If more than one `hooks.json` file exists, Codex loads all matching hooks.
4141Higher-precedence config layers do not replace lower-precedence hooks.Higher-precedence config layers don’t replace lower-precedence hooks.
42 42
43## Config shape43## Config shape
44 44
75 ]75 ]
76 }76 }
77 ],77 ],
78 "PermissionRequest": [
79 {
80 "matcher": "Bash",
81 "hooks": [
82 {
83 "type": "command",
84 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\"",
85 "statusMessage": "Checking approval request"
86 }
87 ]
88 }
89 ],
78 "PostToolUse": [90 "PostToolUse": [
79 {91 {
80 "matcher": "Bash",92 "matcher": "Bash",
133 145
134| Event | What `matcher` filters | Notes |146| Event | What `matcher` filters | Notes |
135| --- | --- | --- |147| --- | --- | --- |
148| `PermissionRequest` | tool name | Current Codex runtime only emits `Bash`. |
136| `PostToolUse` | tool name | Current Codex runtime only emits `Bash`. |149| `PostToolUse` | tool name | Current Codex runtime only emits `Bash`. |
137| `PreToolUse` | tool name | Current Codex runtime only emits `Bash`. |150| `PreToolUse` | tool name | Current Codex runtime only emits `Bash`. |
138| `SessionStart` | start source | Current runtime values are `startup` and `resume`. |151| `SessionStart` | start source | Current runtime values are `startup` and `resume`. |
146- `Edit|Write`159- `Edit|Write`
147 160
148That last example is still a valid regex, but current Codex `PreToolUse` and161That last example is still a valid regex, but current Codex `PreToolUse` and
149162`PostToolUse` events only emit `Bash`, so it will not match anything today.`PostToolUse` events only emit `Bash`, so it won’t match anything today.
150 163
151## Common input fields164## Common input fields
152 165
189 202
190Exit `0` with no output is treated as success and Codex continues.203Exit `0` with no output is treated as success and Codex continues.
191 204
192205`PreToolUse` supports `systemMessage`, but `continue`, `stopReason`, and`PreToolUse` and `PermissionRequest` support `systemMessage`, but `continue`,
193206`suppressOutput` are not currently supported for that event.`stopReason`, and `suppressOutput` aren't currently supported for those events.
194 207
195`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.208`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.
196`suppressOutput` is parsed but not currently supported for that event.209`suppressOutput` is parsed but not currently supported for that event.
278`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and291`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and
279`suppressOutput` are parsed but not supported yet, so they fail open.292`suppressOutput` are parsed but not supported yet, so they fail open.
280 293
294### PermissionRequest
295
296Work in progress
297
298`PermissionRequest` runs when Codex is about to ask for approval, such as a
299shell escalation or managed-network approval. It can allow the request, deny
300the request, or decline to decide and let the normal approval prompt continue.
301It doesn't run for commands that don't need approval.
302
303`matcher` is applied to `tool_name`, which currently always equals `Bash`.
304
305Fields in addition to [Common input fields](#common-input-fields):
306
307| Field | Type | Meaning |
308| --- | --- | --- |
309| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |
310| `tool_name` | `string` | Currently always `Bash` |
311| `tool_input.command` | `string` | Shell command associated with the approval request |
312| `tool_input.description` | `string | null` | Human-readable approval reason, when Codex has one |
313
314Plain text on `stdout` is ignored.
315
316To approve the request, return:
317
318```json
319{
320 "hookSpecificOutput": {
321 "hookEventName": "PermissionRequest",
322 "decision": {
323 "behavior": "allow"
324 }
325 }
326}
327```
328
329To deny the request, return:
330
331```json
332{
333 "hookSpecificOutput": {
334 "hookEventName": "PermissionRequest",
335 "decision": {
336 "behavior": "deny",
337 "message": "Blocked by repository policy."
338 }
339 }
340}
341```
342
343If multiple matching hooks return decisions, any `deny` wins. Otherwise, an
344`allow` lets the request proceed without surfacing the approval prompt. If no
345matching hook decides, Codex uses the normal approval flow.
346
347Don't return `updatedInput`, `updatedPermissions`, or `interrupt` for
348`PermissionRequest`; those fields are reserved for future behavior and fail
349closed today.
350
281### PostToolUse351### PostToolUse
282 352
283Work in progress353Work in progress
284 354
285355Currently `PostToolUse` only supports Bash tool results. It is not limited toCurrently `PostToolUse` only supports Bash tool results. It’s not limited to
286commands that exit successfully: non-interactive `exec_command` calls can still356commands that exit successfully: non-interactive `exec_command` calls can still
287357trigger `PostToolUse` when Codex emits a Bash post-tool payload. It cannot undotrigger `PostToolUse` when Codex emits a Bash post-tool payload. It can’t undo
288side effects from the command that already ran.358side effects from the command that already ran.
289 359
290This doesn't intercept all shell calls yet, only the simple ones. The newer360This doesn't intercept all shell calls yet, only the simple ones. The newer
321 391
322That `additionalContext` text is added as extra developer context.392That `additionalContext` text is added as extra developer context.
323 393
324394For this event, `decision: "block"` does not undo the completed Bash command.For this event, `decision: "block"` doesn't undo the completed Bash command.
325Instead, Codex records the feedback, replaces the tool result with that395Instead, Codex records the feedback, replaces the tool result with that
326feedback, and continues the model from the hook-provided message.396feedback, and continues the model from the hook-provided message.
327 397
336 406
337### UserPromptSubmit407### UserPromptSubmit
338 408
339409`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
340 410
341Fields in addition to [Common input fields](#common-input-fields):411Fields in addition to [Common input fields](#common-input-fields):
342 412
343| Field | Type | Meaning |413| Field | Type | Meaning |
344| --- | --- | --- |414| --- | --- | --- |
345| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |415| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |
346416| `prompt` | `string` | User prompt that is about to be sent || `prompt` | `string` | User prompt that's about to be sent |
347 417
348Plain text on `stdout` is added as extra developer context.418Plain text on `stdout` is added as extra developer context.
349 419
374 444
375### Stop445### Stop
376 446
377447`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
378 448
379Fields in addition to [Common input fields](#common-input-fields):449Fields in addition to [Common input fields](#common-input-fields):
380 450
399 469
400You can also use exit code `2` and write the continuation reason to `stderr`.470You can also use exit code `2` and write the continuation reason to `stderr`.
401 471
402472For this event, `decision: "block"` does not reject the turn. Instead, it tellsFor this event, `decision: "block"` doesn't reject the turn. Instead, it tells
403Codex to continue and automatically creates a new continuation prompt that acts473Codex to continue and automatically creates a new continuation prompt that acts
404as a new user prompt, using your `reason` as that prompt text.474as a new user prompt, using your `reason` as that prompt text.
405 475