hooks.md +104 −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
43Project-local hooks load only when the project `.codex/` layer is trusted. In
44untrusted projects, Codex still loads user and system hooks from their own
45active config layers.
42 46
43## Config shape47## Config shape
44 48
75 ]79 ]
76 }80 }
77 ],81 ],
82 "PermissionRequest": [
83 {
84 "matcher": "Bash",
85 "hooks": [
86 {
87 "type": "command",
88 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\"",
89 "statusMessage": "Checking approval request"
90 }
91 ]
92 }
93 ],
78 "PostToolUse": [94 "PostToolUse": [
79 {95 {
80 "matcher": "Bash",96 "matcher": "Bash",
133 149
134| Event | What `matcher` filters | Notes |150| Event | What `matcher` filters | Notes |
135| --- | --- | --- |151| --- | --- | --- |
152| `PermissionRequest` | tool name | Current Codex runtime only emits `Bash`. |
136| `PostToolUse` | tool name | Current Codex runtime only emits `Bash`. |153| `PostToolUse` | tool name | Current Codex runtime only emits `Bash`. |
137| `PreToolUse` | tool name | Current Codex runtime only emits `Bash`. |154| `PreToolUse` | tool name | Current Codex runtime only emits `Bash`. |
138| `SessionStart` | start source | Current runtime values are `startup` and `resume`. |155| `SessionStart` | start source | Current runtime values are `startup` and `resume`. |
146- `Edit|Write`163- `Edit|Write`
147 164
148That last example is still a valid regex, but current Codex `PreToolUse` and165That last example is still a valid regex, but current Codex `PreToolUse` and
149166`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 167
151## Common input fields168## Common input fields
152 169
189 206
190Exit `0` with no output is treated as success and Codex continues.207Exit `0` with no output is treated as success and Codex continues.
191 208
192209`PreToolUse` supports `systemMessage`, but `continue`, `stopReason`, and`PreToolUse` and `PermissionRequest` support `systemMessage`, but `continue`,
193210`suppressOutput` are not currently supported for that event.`stopReason`, and `suppressOutput` aren't currently supported for those events.
194 211
195`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.212`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.
196`suppressOutput` is parsed but not currently supported for that event.213`suppressOutput` is parsed but not currently supported for that event.
225 242
226### PreToolUse243### PreToolUse
227 244
245Work in progress
246
228Currently `PreToolUse` only supports Bash tool interception. The model can247Currently `PreToolUse` only supports Bash tool interception. The model can
229still work around this by writing its own script to disk and then running that248still 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 complete249script with Bash, so treat this as a useful guardrail rather than a complete
231250enforcement boundary.enforcement boundary
251
252This doesn't intercept all shell calls yet, only the simple ones. The newer
253 `unified_exec` mechanism allows richer streaming stdin/stdout handling of
254shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,
255Write, WebSearch, or other non-shell tool calls.
232 256
233`matcher` is applied to `tool_name`, which currently always equals `Bash`.257`matcher` is applied to `tool_name`, which currently always equals `Bash`.
234 258
271`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and295`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and
272`suppressOutput` are parsed but not supported yet, so they fail open.296`suppressOutput` are parsed but not supported yet, so they fail open.
273 297
298### PermissionRequest
299
300Work in progress
301
302`PermissionRequest` runs when Codex is about to ask for approval, such as a
303shell escalation or managed-network approval. It can allow the request, deny
304the request, or decline to decide and let the normal approval prompt continue.
305It doesn't run for commands that don't need approval.
306
307`matcher` is applied to `tool_name`, which currently always equals `Bash`.
308
309Fields in addition to [Common input fields](#common-input-fields):
310
311| Field | Type | Meaning |
312| --- | --- | --- |
313| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |
314| `tool_name` | `string` | Currently always `Bash` |
315| `tool_input.command` | `string` | Shell command associated with the approval request |
316| `tool_input.description` | `string | null` | Human-readable approval reason, when Codex has one |
317
318Plain text on `stdout` is ignored.
319
320To approve the request, return:
321
322```json
323{
324 "hookSpecificOutput": {
325 "hookEventName": "PermissionRequest",
326 "decision": {
327 "behavior": "allow"
328 }
329 }
330}
331```
332
333To deny the request, return:
334
335```json
336{
337 "hookSpecificOutput": {
338 "hookEventName": "PermissionRequest",
339 "decision": {
340 "behavior": "deny",
341 "message": "Blocked by repository policy."
342 }
343 }
344}
345```
346
347If multiple matching hooks return decisions, any `deny` wins. Otherwise, an
348`allow` lets the request proceed without surfacing the approval prompt. If no
349matching hook decides, Codex uses the normal approval flow.
350
351Don't return `updatedInput`, `updatedPermissions`, or `interrupt` for
352`PermissionRequest`; those fields are reserved for future behavior and fail
353closed today.
354
274### PostToolUse355### PostToolUse
275 356
276357Currently `PostToolUse` only supports Bash tool results. It is not limited toWork in progress
358
359Currently `PostToolUse` only supports Bash tool results. It’s not limited to
277commands that exit successfully: non-interactive `exec_command` calls can still360commands that exit successfully: non-interactive `exec_command` calls can still
278361trigger `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.362side effects from the command that already ran.
280 363
364This doesn't intercept all shell calls yet, only the simple ones. The newer
365 `unified_exec` mechanism allows richer streaming stdin/stdout handling of
366shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,
367Write, WebSearch, or other non-shell tool calls.
368
281`matcher` is applied to `tool_name`, which currently always equals `Bash`.369`matcher` is applied to `tool_name`, which currently always equals `Bash`.
282 370
283Fields in addition to [Common input fields](#common-input-fields):371Fields in addition to [Common input fields](#common-input-fields):
307 395
308That `additionalContext` text is added as extra developer context.396That `additionalContext` text is added as extra developer context.
309 397
310398For 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 that399Instead, Codex records the feedback, replaces the tool result with that
312feedback, and continues the model from the hook-provided message.400feedback, and continues the model from the hook-provided message.
313 401
322 410
323### UserPromptSubmit411### UserPromptSubmit
324 412
325413`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
326 414
327Fields in addition to [Common input fields](#common-input-fields):415Fields in addition to [Common input fields](#common-input-fields):
328 416
329| Field | Type | Meaning |417| Field | Type | Meaning |
330| --- | --- | --- |418| --- | --- | --- |
331| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |419| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |
332420| `prompt` | `string` | User prompt that is about to be sent || `prompt` | `string` | User prompt that's about to be sent |
333 421
334Plain text on `stdout` is added as extra developer context.422Plain text on `stdout` is added as extra developer context.
335 423
360 448
361### Stop449### Stop
362 450
363451`matcher` is not currently used for this event.`matcher` isn't currently used for this event.
364 452
365Fields in addition to [Common input fields](#common-input-fields):453Fields in addition to [Common input fields](#common-input-fields):
366 454
385 473
386You can also use exit code `2` and write the continuation reason to `stderr`.474You can also use exit code `2` and write the continuation reason to `stderr`.
387 475
388476For 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 acts477Codex to continue and automatically creates a new continuation prompt that acts
390as a new user prompt, using your `reason` as that prompt text.478as a new user prompt, using your `reason` as that prompt text.
391 479