{"slug": "codex-hooks-skill", "title": "Codex Hooks SKILL", "summary": "OpenAI's Codex now supports hooks, an extensibility framework that lets developers inject custom scripts into the agent's loop at lifecycle events such as PreToolUse, PostToolUse, SessionStart, and Stop. Hooks are discovered from hooks.json or inline [hooks] tables in config.toml at user and project config layers, and non-managed command hooks must be reviewed and trusted via the /hooks CLI command before they run.", "body_md": "| name | codex-hook | \n|---|---|\n| description | Helps users and agents understand how to write codex hooks. Use this whenever hooks are mentioned. | \n\nFor the complete documentation index, see [llms.txt](https://learn.chatgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL\nHooks are an extensibility framework for Codex. They allow\nyou to inject your own scripts into the agentic loop, enabling features such as:\n\n- Send the chat to a custom logging/analytics engine\n- Scan your team's prompts to block accidentally pasting API keys\n- Summarize chats to create persistent memories automatically\n- Run a custom validation check when a chat turn stops, enforcing standards\n- Customize prompting when in a certain directory\n\nRuntime behavior to keep in mind:\n\n- Matching hooks from multiple files all run.\n- Multiple matching command hooks for the same event are launched concurrently, so one hook can't prevent another matching hook from starting.\n- Non-managed command hooks must be reviewed and trusted before they run.\n\nHooks run at different points in a conversation:\n\n| When | Hooks | \n|---|---|\n| During a turn | `PreToolUse` ,`PermissionRequest` ,`PostToolUse` ,`PreCompact` ,`PostCompact` ,`UserPromptSubmit` ,`SubagentStop` ,`Stop` | \n| When a session or subagent starts | `SessionStart` ,`SubagentStart` | \n| When the main thread ends | `SessionEnd` (doesn't run for subagents) | \n\nCodex discovers hooks next to active config layers in either of these forms:\n\n- `hooks.json`\n- inline `[hooks]` tables inside`config.toml`\n\nInstalled plugins can also bundle lifecycle config through their plugin\nmanifest or a default `hooks/hooks.json` file. See [Build\nplugins](https://developers.openai.com/plugins/build/plugins#bundled-mcp-servers-and-lifecycle-hooks) for the\nplugin packaging rules.\n\nIn practice, the four most useful locations are:\n\n- `~/.codex/hooks.json`\n- `~/.codex/config.toml`\n- `<repo>/.codex/hooks.json`\n- `<repo>/.codex/config.toml`\n\nIf more than one hook source exists, Codex loads all matching hooks.\nHigher-precedence config layers don't replace lower-precedence hooks.\nIf a single layer contains both `hooks.json` and inline `[hooks]`, Codex\nmerges them and warns at startup. Prefer one representation per layer.\n\nCodex can also discover hooks bundled with enabled plugins. Plugin-bundled hooks load alongside other hook sources and use the same trust-review flow as other non-managed hooks.\n\nProject-local hooks load only when the project `.codex/` layer is trusted. In\nuntrusted projects, Codex still loads user and system hooks from their own\nactive config layers.\n\nCodex lists configured hooks before deciding which ones can run. Before a non-managed command hook can run, Codex requires you to review and trust the exact hook definition. Codex records trust against the hook's current hash, so new or changed hooks are marked for review and skipped until trusted.\n\nUse `/hooks` in the CLI to inspect hook sources, review new or changed hooks,\ntrust hooks, or disable individual non-managed hooks. If hooks need review at\nstartup, Codex prints a warning that tells you to open `/hooks`.\n\nManaged hooks from system, MDM, cloud, or `requirements.toml` sources are marked\nas managed, trusted by policy, and can't be disabled from the user hook browser.\n\nFor one-off automation that already vets hook sources outside Codex, pass\n`--dangerously-bypass-hook-trust` to run enabled hooks without requiring\npersisted hook trust for that invocation.\n\nHooks are organized in three levels:\n\n- A hook event such as `PreToolUse` ,`PostToolUse` ,`PreCompact` ,`SubagentStart` , or`Stop`\n- A matcher group that decides when that event matches\n- One or more hook handlers that run when the matcher group matches\n\n```\n{\n  \"description\": \"Optional lifecycle hooks for this workspace.\",\n  \"hooks\": {\n    \"SessionStart\": [\n      {\n        \"matcher\": \"startup|resume\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"python3 ~/.codex/hooks/session_start.py\",\n            \"statusMessage\": \"Loading session notes\",\n            \"additionalContextLimit\": 5000\n          }\n        ]\n      }\n    ],\n    \"SessionEnd\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"python3 ~/.codex/hooks/session_end.py\",\n            \"timeout\": 3\n          }\n        ]\n      }\n    ],\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/python3 \\\"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\\\"\",\n            \"statusMessage\": \"Checking Bash command\"\n          }\n        ]\n      }\n    ],\n    \"PermissionRequest\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/python3 \\\"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\\\"\",\n            \"statusMessage\": \"Checking approval request\"\n          }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/python3 \\\"$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py\\\"\",\n            \"statusMessage\": \"Reviewing Bash output\"\n          }\n        ]\n      }\n    ],\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/python3 \\\"$(git rev-parse --show-toplevel)/.codex/hooks/user_prompt_submit_data_flywheel.py\\\"\"\n          }\n        ]\n      }\n    ],\n    \"Stop\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/python3 \\\"$(git rev-parse --show-toplevel)/.codex/hooks/stop_continue.py\\\"\",\n            \"timeout\": 30\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nNotes:\n\n- `description` is optional top-level metadata for a`hooks.json` file. It\ndoesn't change which hooks run.\n- `timeout` is in seconds.\n- If `timeout` is omitted, Codex uses`600` seconds for most hooks.\n  - `SessionEnd` uses`1` second by default and supports up to`3` seconds.\n- `statusMessage` is optional.\n- `additionalContextLimit` sets how much`additionalContext` a command hook can\nsend to the model before Codex saves the full text to disk and sends a shorter\npreview instead. See[Large hook output](#large-hook-output) .\n- `commandWindows` is an optional Windows-only command override. In TOML, use`command_windows` or`commandWindows` .\n- Set `async` to`true` to[run a command hook in the\nbackground](#run-hooks-in-the-background) .\n- Only `type: \"command\"` handlers run today.`prompt` and`agent` handlers are\nparsed but skipped.\n- Commands run with the session `cwd` as their working directory.\n- For repo-local hooks, prefer resolving from the git root instead of using a\nrelative path such as `.codex/hooks/...` . Codex may be started from a\nsubdirectory, and a git-root-based path keeps the hook location stable.\n\nEquivalent inline TOML in `config.toml`:\n\n```\n[[hooks.SessionStart]]\nmatcher = \"^compact$\"\n\n[[hooks.SessionStart.hooks]]\ntype = \"command\"\ncommand = '/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/session_start.py\"'\nadditionalContextLimit = 5000\n\n[[hooks.PreToolUse]]\nmatcher = \"^Bash$\"\n\n[[hooks.PreToolUse.hooks]]\ntype = \"command\"\ncommand = '/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"'\ntimeout = 30\nstatusMessage = \"Checking Bash command\"\n\n[[hooks.PostToolUse]]\nmatcher = \"^Bash$\"\n\n[[hooks.PostToolUse.hooks]]\ntype = \"command\"\ncommand = '/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py\"'\ntimeout = 30\nstatusMessage = \"Reviewing Bash output\"\n```\n\nHooks are enabled by default. To turn them off in `config.toml`, set:\n\n```\n[features]\nhooks = false\n```\n\nUse `hooks` as the canonical feature key. `codex_hooks` still works as a\ndeprecated alias. Admins can force hooks off the same way in\n`requirements.toml` with `[features].hooks = false`.\n\nEnterprise-managed requirements can also define hooks inline under `[hooks]`.\nThis is useful when admins want to enforce the hook configuration while\ndelivering the actual scripts through MDM or another device-management system.\nTo enforce managed hooks even for users who disabled hooks locally, pin\n`[features].hooks = true` in `requirements.toml` alongside `[hooks]`. To ignore\nuser, project, session, and plugin hooks while still allowing administrator\nmanaged hooks, set `allow_managed_hooks_only = true`.\n\n```\nallow_managed_hooks_only = true\n\n[features]\nhooks = true\n\n[hooks]\nmanaged_dir = \"/enterprise/hooks\"\nwindows_managed_dir = 'C:\\enterprise\\hooks'\n\n[[hooks.PreToolUse]]\nmatcher = \"^Bash$\"\n\n[[hooks.PreToolUse.hooks]]\ntype = \"command\"\ncommand = \"python3 /enterprise/hooks/pre_tool_use_policy.py\"\ncommand_windows = 'py -3 C:\\enterprise\\hooks\\pre_tool_use_policy.py'\ntimeout = 30\nstatusMessage = \"Checking managed Bash command\"\n```\n\nNotes for managed hooks:\n\n- `managed_dir` is used on macOS and Linux.\n- `windows_managed_dir` is used on Windows.\n- Codex doesn't distribute the scripts in `managed_dir` ; your enterprise\ntooling must install and update them separately.\n- Managed hook commands should use absolute script paths under the configured managed directory.\n- `allow_managed_hooks_only = true` skips hooks from user, project, session, and\nplugin sources, but still loads managed hooks from`requirements.toml` and\nother managed config layers.\n\nWhen a plugin is enabled, Codex can load lifecycle hooks from that plugin alongside user, project, and managed hooks.\n\nBy default, Codex looks for `hooks/hooks.json` inside the plugin root. A plugin\nmanifest can override that default with a `hooks` entry in\n`.codex-plugin/plugin.json`. The manifest entry can be a `./`-prefixed path, an\narray of `./`-prefixed paths, an inline hooks object, or an array of inline\nhooks objects.\n\n```\n{\n  \"name\": \"repo-policy\",\n  \"hooks\": \"./hooks/hooks.json\"\n}\n```\n\nManifest hook paths are resolved relative to the plugin root and must stay\ninside that root. If a manifest defines `hooks`, Codex uses those manifest\nentries instead of the default `hooks/hooks.json`.\n\nPlugin hook commands receive these environment variables:\n\n- `PLUGIN_ROOT` is a Codex-specific extension that points to the installed\nplugin root.\n- `PLUGIN_DATA` is a Codex-specific extension that points to the plugin's\nwritable data directory.\n- Codex also sets `CLAUDE_PLUGIN_ROOT` and`CLAUDE_PLUGIN_DATA` for\ncompatibility with existing plugin hooks.\n\nPlugin hooks use the same event schema as other hooks. Installing or enabling a plugin doesn't automatically trust its hooks; Codex skips plugin-bundled hooks until you review and trust the current hook definition.\n\nThe `matcher` field is a regex string that filters when hooks fire. Use `\"*\"`,\n`\"\"`, or omit `matcher` entirely to match every occurrence of a supported\nevent.\n\nOnly some current Codex events honor `matcher`:\n\n| Event | What `matcher` filters | Notes | \n|---|---|---|\n| `PermissionRequest` | tool name | Support includes `Bash` ,`apply_patch` *, and MCP tool names | \n| `PostToolUse` | tool name | See [Tool coverage](#tool-coverage) | \n| `PostCompact` | compaction trigger | Values are `manual` or`auto` | \n| `PreCompact` | compaction trigger | Values are `manual` or`auto` | \n| `PreToolUse` | tool name | See [Tool coverage](#tool-coverage) | \n| `SessionEnd` | end reason | Currently only `other` | \n| `SessionStart` | start source | Values are `startup` ,`resume` ,`clear` , and`compact` | \n| `SubagentStart` | subagent type | Values depend on the subagent that starts | \n| `SubagentStop` | subagent type | Values depend on the subagent that stops | \n| `UserPromptSubmit` | not supported | Any configured `matcher` is ignored for this event | \n| `Stop` | not supported | Any configured `matcher` is ignored for this event | \n\n*For `apply_patch`, `matcher` values can also use `Edit` or `Write`.\n\nExamples:\n\n- `Bash`\n- `^apply_patch$`\n- `Edit|Write`\n- `mcp__filesystem__read_file`\n- `mcp__filesystem__.*`\n- `startup|resume|clear|compact`\n- `manual|auto`\n\n`PreToolUse` and `PostToolUse` can observe more than shell and MCP calls. Most\nlocal function tools use the same hook path, so you can match their tool name,\ninspect their JSON arguments, and, for `PreToolUse`, block or rewrite the call.\n\n| Tool path | `PreToolUse` | `PostToolUse` | Notes | \n|---|---|---|---|\n| Shell commands | Yes | Yes | Match as `Bash` . | \n| Unified exec ( `exec_command` ) | Yes | Yes | Match as `Bash` . A later`write_stdin` poll can deliver the original command's`PostToolUse` when that command finishes. | \n| `apply_patch` | Yes | Yes | Match as `apply_patch` ,`Edit` , or`Write` . | \n| MCP tools | Yes | Yes | Match the MCP tool name, such as `mcp__filesystem__read_file` . | \n| Other local function tools | Yes | Yes | Match the function tool name, such as `update_plan` .`spawn_agent` also matches`Agent` . | \n| Hosted tools, such as `WebSearch` | No | No | These don't use the local function-tool hook path. | \n\n`write_stdin` is transport for an existing unified-exec session. It doesn't run\n`PreToolUse` again when it sends input or polls a command that already passed\n`PreToolUse`.\n\nSome specialized tool paths can opt out of the default hook path. Treat tool hooks as a useful guardrail, not a complete enforcement boundary.\n\nEvery command hook receives one JSON object on `stdin`.\n\nThese are the shared fields you will usually use:\n\n| Field | Type | Meaning | \n|---|---|---|\n| `session_id` | `string` | Current Codex session id. Subagent hooks use the parent session id. | \n| `transcript_path` | `string \\| null` | Path to the session transcript file, if any | \n| `cwd` | `string` | Working directory for the session | \n| `hook_event_name` | `string` | Current hook event name | \n| `model` | `string` | Codex-specific extension. Active model slug | \n\nTurn-scoped hooks list `turn_id` as a Codex-specific extension in their\nevent-specific tables.\n\n`SessionStart`, `PreToolUse`, `PermissionRequest`, `PostToolUse`,\n`UserPromptSubmit`, `SubagentStart`, `SubagentStop`, and `Stop` also include\n`permission_mode`, which describes the current permission mode as `default`,\n`acceptEdits`, `plan`, `dontAsk`, or `bypassPermissions`.\n\n`transcript_path` points to a chat transcript for convenience, but the\ntranscript format isn't a stable interface for hooks and may change over time.\n\nIf you need the full wire format, see [Schemas](#schemas).\n\n`SessionStart`, `PreCompact`, `PostCompact`, `UserPromptSubmit`,\n`SubagentStop`, and `Stop` support these shared JSON fields. `SubagentStart`\naccepts the same shape for `systemMessage` and hook-specific context, but\n`continue: false` doesn't stop the subagent:\n\n```\n{\n  \"continue\": true,\n  \"stopReason\": \"optional\",\n  \"systemMessage\": \"optional\",\n  \"suppressOutput\": false\n}\n```\n\n| Field | Effect | \n|---|---|\n| `continue` | If `false` , marks that hook run as stopped | \n| `stopReason` | Recorded as the reason for stopping | \n| `systemMessage` | Surfaced as a warning in the UI or event stream | \n| `suppressOutput` | Parsed today but not yet implemented | \n\nExit `0` with no output is treated as success and Codex continues.\n\n`PreToolUse` and `PermissionRequest` support `systemMessage`, but `continue`,\n`stopReason`, and `suppressOutput` aren't currently supported for those events.\nIf a `PreToolUse` hook returns one of those unsupported fields, Codex marks\nthat hook run as failed, reports the error, and continues the tool call.\n\n`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.\n`suppressOutput` is parsed but not currently supported for that event.\n\nBy default, Codex limits each model-visible hook-output message to roughly\n2,500 tokens. If a hook returns more, Codex saves the full text under\n`<temp_dir>/hook_outputs/<session_id>/<uuid>.txt` and gives the model a\nhead-and-tail preview with the saved-file path. This behavior is called\n**spilling**: Codex stores oversized output on disk and replaces it with a\nshorter, model-visible preview. If the file can't be written, the model still\nreceives a truncated preview.\n\nKeep hook and plugin context concise. Context from multiple hooks and plugins\nadds up and can degrade model performance. Raising `additionalContextLimit`\nincreases that risk. Avoid setting the limit to `0` unless the hook enforces a\nstrict output cap; otherwise, a single hook can consume the entire context\nwindow.\n\nFor any command hook that returns `additionalContext`, set\n`additionalContextLimit` on the handler to customize the approximate token\nthreshold:\n\n```\n{\n  \"type\": \"command\",\n  \"command\": \"python3 ~/.codex/hooks/session_start.py\",\n  \"additionalContextLimit\": 5000\n}\n```\n\nOmit `additionalContextLimit` to use the default `2500`-token threshold. Use a\npositive integer to select a different threshold, or `0` to pass the handler's\ncomplete additional context directly to the model. Codex evaluates each\nmatching handler independently. For events that can't produce additional\ncontext, Codex ignores `additionalContextLimit` and reports a configuration\nwarning.\n\nThe setting applies only to `additionalContext`. Tool feedback and continuation\nprompts keep the default limit.\n\nBecause oversized output can be written to disk, avoid returning secrets or other sensitive data in hook output.\n\nBy default, Codex waits for a command hook to finish before continuing the\noperation that triggered it. Set `async` to `true` to run a command hook in the\nbackground while Codex continues.\n\nAdd `\"async\": true` to a command handler in `hooks.json`:\n\n```\n{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"python3 ~/.codex/hooks/post_tool_use.py\",\n            \"async\": true,\n            \"timeout\": 120\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nFor an inline hook in `config.toml`, set `async = true`:\n\n```\n[[hooks.PostToolUse]]\nmatcher = \"Bash\"\n\n[[hooks.PostToolUse.hooks]]\ntype = \"command\"\ncommand = \"python3 ~/.codex/hooks/post_tool_use.py\"\nasync = true\ntimeout = 120\n```\n\nBackground hooks use the same input, matcher, trust review, timeout, and\n[large-output handling](#large-hook-output) as synchronous command hooks. As\nwith other command hooks, `timeout` is measured in seconds and defaults to\n`600`.\n\nWhen a background hook finishes, Codex delivers supported informational output at the next safe point in the conversation:\n\n- If a turn is active, Codex waits for the current model request and tool calls to finish, then makes the output available to the next model request in that turn.\n- If no turn is active, Codex waits until the next user turn. Finishing a background hook doesn't start a new turn.\n\nUse the same event-specific JSON output as a synchronous hook. Codex adds\n`additionalContext` to the model's context and surfaces `systemMessage` as a\nwarning.\n\nBackground hooks can't block, approve, rewrite, or otherwise control the operation that triggered them. Use synchronous hooks for tool policies, permission decisions, prompt rejection, or turn continuation.\n\n- Codex runs up to eight background hooks concurrently per session. Additional hooks wait until a running hook finishes.\n- Each matching invocation runs independently, and background hooks can finish in a different order than they started.\n- When the session ends, Codex cancels unfinished background hooks and discards output that hasn't been delivered.\n- `SessionEnd` hooks always run synchronously.\n\n`matcher` is applied to `source` for this event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `source` | `string` | How the session started: `startup` ,`resume` ,`clear` , or`compact` | \n\nPlain text on `stdout` is added as extra developer context.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields) and this\nhook-specific shape:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"SessionStart\",\n    \"additionalContext\": \"Load the workspace conventions before editing.\"\n  }\n}\n```\n\nThat `additionalContext` text is added as extra developer context.\n\nAfter Codex compacts a root session, `SessionStart` hooks that match\n`source: \"compact\"` run before the next model request. This also applies when\nautomatic compaction happens in the middle of a turn: Codex delivers the hook's\nadditional context to the immediate continuation instead of waiting for a\nlater user turn. If the hook returns `continue: false`, Codex ends the turn\nwithout sending another model request.\n\n`SessionEnd` lets you run a command when a session ends, such as saving final\nnotes or cleaning up files. It runs for the main thread when you archive or\ndelete a conversation that's still open, when Codex closes normally, or after a\nconversation has been idle and isn't open in any connected client for 30\nminutes. It won't run for subagents.\n\nSwitching away from a conversation or calling `thread/unsubscribe` doesn't end\nthe session right away, so it won't immediately run `SessionEnd`. Your hook can\nstill read the session transcript while it runs.\n\n`matcher` filters `reason` for this event. For now, `reason` is always `other`.\nYou can omit `matcher` or use `other` to run on every `SessionEnd` event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `reason` | `string` | Why the session ended: `other` | \n\nFor example, a `SessionEnd` command receives:\n\n```\n{\n  \"session_id\": \"thr_123\",\n  \"transcript_path\": \"/workspace/.codex/rollout.jsonl\",\n  \"cwd\": \"/workspace\",\n  \"hook_event_name\": \"SessionEnd\",\n  \"reason\": \"other\"\n}\n```\n\n`SessionEnd` hooks always run synchronously, even when `async` is `true`. They\nare advisory, so their output won't steer Codex or keep the thread open. If a\ncommand times out or exits with an error, Codex reports it as a hook failure.\n\n`matcher` is applied to `agent_type` for this event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `agent_id` | `string` | Identifier for the subagent | \n| `agent_type` | `string` | Subagent type or profile | \n| `permission_mode` | `string` | Current permission mode | \n\nPlain text on `stdout` is added as extra developer context for the subagent.\n\nJSON on `stdout` supports `systemMessage` and this hook-specific shape:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"SubagentStart\",\n    \"additionalContext\": \"Review the repository test conventions first.\"\n  }\n}\n```\n\nThat `additionalContext` text is added as extra developer context for the\nsubagent. `continue: false` is parsed for compatibility, but it doesn't stop the\nsubagent from starting.\n\n`PreToolUse` can intercept Bash, file edits performed through `apply_patch`,\nMCP tool calls, and other local function tools. See [Tool\ncoverage](#tool-coverage) for the supported paths and exceptions.\n\n`matcher` is applied to `tool_name` and matcher aliases. For file edits through\n`apply_patch`, `matcher` values can use `apply_patch`, `Edit`, or `Write`; hook input\nstill reports `tool_name: \"apply_patch\"`.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `tool_name` | `string` | Canonical hook tool name, such as `Bash` ,`apply_patch` , or an MCP name like`mcp__fs__read` | \n| `tool_use_id` | `string` | Tool-call id for this invocation | \n| `tool_input` | `JSON value` | Tool-specific input. `Bash` and`apply_patch` use`tool_input.command` . MCP and other local function tools send their arguments. | \n\nPlain text on `stdout` is ignored.\n\nJSON on `stdout` can use `systemMessage`. To deny a supported tool call, return\nthis hook-specific shape:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"Destructive command blocked by hook.\"\n  }\n}\n```\n\nCodex also accepts this older block shape:\n\n```\n{\n  \"decision\": \"block\",\n  \"reason\": \"Destructive command blocked by hook.\"\n}\n```\n\nYou can also use exit code `2` and write the blocking reason to `stderr`.\n\nTo add model-visible context without blocking, return\n`hookSpecificOutput.additionalContext`:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"additionalContext\": \"The pending command touches generated files.\"\n  }\n}\n```\n\nTo rewrite a supported tool call without blocking, return\n`permissionDecision: \"allow\"` with `updatedInput`:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"allow\",\n    \"updatedInput\": {\n      \"command\": \"echo rewritten\"\n    }\n  }\n}\n```\n\nFor Bash commands and `apply_patch`, `updatedInput` must include a string\n`command` field. For MCP and other local function tools, `updatedInput` is the\nreplacement arguments object. Return `updatedInput` only with\n`permissionDecision: \"allow\"`; other `updatedInput` shapes are reported as\nerrors.\n\n`permissionDecision: \"ask\"`, legacy `decision: \"approve\"`, `continue: false`,\n`stopReason`, and `suppressOutput` are parsed but not supported yet. Codex marks\nthe hook run as failed, reports the error, and continues the tool call.\n\n`PermissionRequest` runs when Codex is about to ask for approval, such as a\nshell escalation or managed-network approval. It can allow the request, deny\nthe request, or decline to decide and let the normal approval prompt continue.\nIt doesn't run for commands that don't need approval.\n\n`matcher` is applied to `tool_name` and matcher aliases. Current canonical\nvalues include `Bash`, `apply_patch`, and MCP tool names such as\n`mcp__server__tool`; `apply_patch` also matches `Edit` and `Write`.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `tool_name` | `string` | Canonical hook tool name, such as `Bash` ,`apply_patch` , or an MCP name like`mcp__fs__read` | \n| `tool_input` | `JSON value` | Tool-specific input. `Bash` and`apply_patch` use`tool_input.command` while MCP tools send all the arguments. | \n| `tool_input.description` | `string \\| null` | Human-readable approval reason, when Codex has one | \n\nPlain text on `stdout` is ignored.\n\nSome tool inputs may include a human-readable description, but don't rely on a\n`tool_input.description` field for every tool.\n\nTo approve the request, return:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PermissionRequest\",\n    \"decision\": {\n      \"behavior\": \"allow\"\n    }\n  }\n}\n```\n\nTo deny the request, return:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PermissionRequest\",\n    \"decision\": {\n      \"behavior\": \"deny\",\n      \"message\": \"Blocked by repository policy.\"\n    }\n  }\n}\n```\n\nIf multiple matching hooks return decisions, any `deny` wins. Otherwise, an\n`allow` lets the request proceed without surfacing the approval prompt. If no\nmatching hook decides, Codex uses the normal approval flow.\n\nDon't return `updatedInput`, `updatedPermissions`, or `interrupt` for\n`PermissionRequest`; those fields are reserved for future behavior and fail\nclosed today.\n\n`PostToolUse` runs after supported tools produce output, including Bash,\n`apply_patch`, MCP tool calls, and other local function tools. For Bash, it\nalso runs after commands that exit with a non-zero status. It can't undo side\neffects from a tool that already ran. See [Tool coverage](#tool-coverage) for\nthe supported paths and exceptions.\n\n`matcher` is applied to `tool_name` and matcher aliases. For file edits through\n`apply_patch`, `matcher` values can use `apply_patch`, `Edit`, or `Write`; hook input\nstill reports `tool_name: \"apply_patch\"`.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `tool_name` | `string` | Canonical hook tool name, such as `Bash` ,`apply_patch` , or an MCP name like`mcp__fs__read` | \n| `tool_use_id` | `string` | Tool-call id for this invocation | \n| `tool_input` | `JSON value` | Tool-specific input. `Bash` and`apply_patch` use`tool_input.command` . MCP and other local function tools send their arguments. | \n| `tool_response` | `JSON value` | Tool-specific output. MCP tools send the MCP call result. Other local function tools normally send their model-facing output. | \n\nPlain text on `stdout` is ignored.\n\nJSON on `stdout` can use `systemMessage` and this hook-specific shape:\n\n```\n{\n  \"decision\": \"block\",\n  \"reason\": \"The Bash output needs review before continuing.\",\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PostToolUse\",\n    \"additionalContext\": \"The command updated generated files.\"\n  }\n}\n```\n\nThat `additionalContext` text is added as extra developer context.\n\nFor this event, `decision: \"block\"` doesn't undo the completed Bash command.\nInstead, Codex records the feedback, replaces the tool result with that\nfeedback, and continues the model from the hook-provided message.\n\nYou can also use exit code `2` and write the feedback reason to `stderr`.\n\nTo stop normal processing of the original tool result after the command has\nalready run, return `continue: false`. Codex will replace the tool result with\nyour feedback or stop text and continue from there.\n\n`updatedMCPToolOutput` and `suppressOutput` are parsed but not supported yet.\nCodex marks the hook run as failed, reports the error, and continues normal\nprocessing of the tool result.\n\nWhen a model uses code mode to call a tool from JavaScript, hook decisions apply\nto that nested call. `PreToolUse` can stop the tool before it runs or rewrite\nits input. A blocking `PostToolUse` can't undo the tool's side effects, but it\ncan keep the original result from reaching the running script.\n\n| Hook result | What code mode sees | \n|---|---|\n| `PreToolUse` blocks | The tool promise rejects before the tool runs. | \n| `PreToolUse` returns`updatedInput` | The tool runs with the rewritten input and the promise resolves with that result. | \n| `PostToolUse` returns`decision: \"block\"` or exits with code`2` | The tool runs, then the promise rejects with the hook reason. | \n| `PostToolUse` returns`continue: false` | Codex uses the hook feedback for the model-visible result, but doesn't reject the nested tool promise. | \n\n`PreCompact` runs before Codex compacts the chat. `matcher` is applied\nto `trigger`, whose values are `manual` and `auto`.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `trigger` | `string` | What triggered compaction: `manual` or`auto` | \n\nPlain text on `stdout` is ignored.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields). If a\nmatching `PreCompact` hook returns `continue: false`, Codex stops before\ncompacting.\n\n`PostCompact` runs after Codex compacts the chat. `matcher` is applied\nto `trigger`, whose values are `manual` and `auto`.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `trigger` | `string` | What triggered compaction: `manual` or`auto` | \n\nPlain text on `stdout` is ignored.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields). If a\nmatching `PostCompact` hook returns `continue: false`, Codex stops after\ncompacting.\n\n`matcher` isn't currently used for this event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `prompt` | `string` | User prompt that's about to be sent | \n\nPlain text on `stdout` is added as extra developer context.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields) and\nthis hook-specific shape:\n\n```\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"UserPromptSubmit\",\n    \"additionalContext\": \"Ask for a clearer reproduction before editing files.\"\n  }\n}\n```\n\nThat `additionalContext` text is added as extra developer context.\n\nTo block the prompt, return:\n\n```\n{\n  \"decision\": \"block\",\n  \"reason\": \"Ask for confirmation before doing that.\"\n}\n```\n\nYou can also use exit code `2` and write the blocking reason to `stderr`.\n\n`matcher` is applied to `agent_type` for this event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `agent_id` | `string` | Identifier for the subagent | \n| `agent_type` | `string` | Subagent type or profile | \n| `agent_transcript_path` | `string \\| null` | Path to the subagent transcript file, if any | \n| `stop_hook_active` | `boolean` | Whether this subagent was already continued | \n| `last_assistant_message` | `string \\| null` | Latest subagent assistant message, if available | \n\n`SubagentStop` expects JSON on `stdout` when it exits `0`. Plain text output is\ninvalid for this event.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields). To ask\nCodex to continue the subagent flow, return:\n\n```\n{\n  \"decision\": \"block\",\n  \"reason\": \"Run one more focused pass inside the subagent.\"\n}\n```\n\nYou can also use exit code `2` and write the continuation reason to `stderr`.\n\nIf any matching `SubagentStop` hook returns `continue: false`, that takes\nprecedence over continuation decisions from other matching `SubagentStop`\nhooks.\n\n`matcher` isn't currently used for this event.\n\nFields in addition to [Common input fields](#common-input-fields):\n\n| Field | Type | Meaning | \n|---|---|---|\n| `turn_id` | `string` | Codex-specific extension. Active Codex turn id | \n| `stop_hook_active` | `boolean` | Whether this turn was already continued by `Stop` | \n| `last_assistant_message` | `string \\| null` | Latest assistant message text, if available | \n\n`Stop` expects JSON on `stdout` when it exits `0`. Plain text output is invalid\nfor this event.\n\nJSON on `stdout` supports [Common output fields](#common-output-fields). To keep\nCodex going, return:\n\n```\n{\n  \"decision\": \"block\",\n  \"reason\": \"Run one more pass over the failing tests.\"\n}\n```\n\nYou can also use exit code `2` and write the continuation reason to `stderr`.\n\nFor this event, `decision: \"block\"` doesn't reject the turn. Instead, it tells\nCodex to continue and automatically creates a new continuation prompt that acts\nas a new user prompt, using your `reason` as that prompt text.\n\nIf any matching `Stop` hook returns `continue: false`, that takes precedence\nover continuation decisions from other matching `Stop` hooks.\n\nThe linked `main` branch schemas may include hook fields that are not in the\ncurrent release. Use this page as the release behavior reference.\n\nIf you need the exact current wire format, see the generated schemas in the\n[Codex GitHub repository](https://github.com/openai/codex/tree/main/codex-rs/hooks/schema/generated).\n\n- string | null", "url": "https://wpnews.pro/news/codex-hooks-skill", "canonical_source": "https://gist.github.com/andmigque/1c97327306b80692fa08b7621518f1c1", "published_at": "2026-10-06 16:44:52+00:00", "updated_at": "2026-10-06 16:49:42.621697+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "agent-protocols"], "entities": ["OpenAI", "Codex"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/codex-hooks-skill", "markdown": "https://wpnews.pro/news/codex-hooks-skill.md", "text": "https://wpnews.pro/news/codex-hooks-skill.txt", "jsonld": "https://wpnews.pro/news/codex-hooks-skill.jsonld"}}