hooks.md +100 −16
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.
225 238
226### PreToolUse239### PreToolUse
227 240
241Work in progress
242
228Currently `PreToolUse` only supports Bash tool interception. The model can243Currently `PreToolUse` only supports Bash tool interception. The model can
229still work around this by writing its own script to disk and then running that244still work around this by writing its own script to disk and then running that
230script with Bash, so treat this as a useful guardrail rather than a complete245script with Bash, so treat this as a useful guardrail rather than a complete
231246enforcement boundary.enforcement boundary
247
248This doesn't intercept all shell calls yet, only the simple ones. The newer
249 `unified_exec` mechanism allows richer streaming stdin/stdout handling of
250shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,
251Write, WebSearch, or other non-shell tool calls.
232 252
233`matcher` is applied to `tool_name`, which currently always equals `Bash`.253`matcher` is applied to `tool_name`, which currently always equals `Bash`.
234 254
271`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and291`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and
272`suppressOutput` are parsed but not supported yet, so they fail open.292`suppressOutput` are parsed but not supported yet, so they fail open.
273 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
274### PostToolUse351### PostToolUse
275 352
276353Currently `PostToolUse` only supports Bash tool results. It is not limited toWork in progress
354
355Currently `PostToolUse` only supports Bash tool results. It’s not limited to
277commands that exit successfully: non-interactive `exec_command` calls can still356commands that exit successfully: non-interactive `exec_command` calls can still
278357trigger `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
279side effects from the command that already ran.358side effects from the command that already ran.
280 359
360This doesn't intercept all shell calls yet, only the simple ones. The newer
361 `unified_exec` mechanism allows richer streaming stdin/stdout handling of
362shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,
363Write, WebSearch, or other non-shell tool calls.
364
281`matcher` is applied to `tool_name`, which currently always equals `Bash`.365`matcher` is applied to `tool_name`, which currently always equals `Bash`.
282 366
283Fields in addition to [Common input fields](#common-input-fields):367Fields in addition to [Common input fields](#common-input-fields):
307 391
308That `additionalContext` text is added as extra developer context.392That `additionalContext` text is added as extra developer context.
309 393
310394For this event, `decision: "block"` does not undo the completed Bash command.For this event, `decision: "block"` doesn't undo the completed Bash command.
311Instead, Codex records the feedback, replaces the tool result with that395Instead, Codex records the feedback, replaces the tool result with that
312feedback, and continues the model from the hook-provided message.396feedback, and continues the model from the hook-provided message.
313 397
322 406
323### UserPromptSubmit407### UserPromptSubmit
324 408
325409`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
326 410
327Fields in addition to [Common input fields](#common-input-fields):411Fields in addition to [Common input fields](#common-input-fields):
328 412
329| Field | Type | Meaning |413| Field | Type | Meaning |
330| --- | --- | --- |414| --- | --- | --- |
331| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |415| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |
332416| `prompt` | `string` | User prompt that is about to be sent || `prompt` | `string` | User prompt that's about to be sent |
333 417
334Plain text on `stdout` is added as extra developer context.418Plain text on `stdout` is added as extra developer context.
335 419
360 444
361### Stop445### Stop
362 446
363447`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
364 448
365Fields in addition to [Common input fields](#common-input-fields):449Fields in addition to [Common input fields](#common-input-fields):
366 450
385 469
386You 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`.
387 471
388472For this event, `decision: "block"` does not reject the turn. Instead, it tellsFor this event, `decision: "block"` doesn't reject the turn. Instead, it tells
389Codex to continue and automatically creates a new continuation prompt that acts473Codex to continue and automatically creates a new continuation prompt that acts
390as a new user prompt, using your `reason` as that prompt text.474as a new user prompt, using your `reason` as that prompt text.
391 475