Codex Hooks: How to Set Them Up and Use Them
Short answer
Short answer: Codex hooks are scripts, or MCP tool calls, that Codex runs at set points in a session, such as before a tool runs or when a turn stops. They are on by default. Add a hooks.json file at ~/.codex/ or <repo>/.codex/, or use inline [hooks] tables in config.toml. Name an event, an optional matcher and a command. Then open /hooks in the Codex CLI to review and trust the hook, because Codex skips new or changed hooks until you do. Source: OpenAI's hooks page, read 4 November 2026.

A developer wants Codex to refuse a destructive shell command. A rule in a prompt is not enough, because the request needs a check at the moment a shell tool runs. A hook can inspect that call and deny it before the command starts.
Before you start
You need the Codex CLI and a Git repository. By the end, you will have a trusted PreToolUse hook that checks every Bash call and can deny a command.
What Codex hooks are
Hooks extend Codex with scripts or MCP tools that run during the agentic loop. A hook can log activity, check a prompt before submission or validate work when a turn stops. Matching hooks from every loaded file run, and command hooks for the same event start together.
Before a non-managed hook can run, Codex requires you to review and trust the exact hook definition.
Official wording: OpenAI, Hooks, read 4 November 2026, developers.openai.com
Trust applies to the hook's current hash. When its definition changes, Codex marks it for review and skips it until you trust the new version.
How hook trust works
Trust is tied to the exact definition. It is not blanket approval for every script with the same filename. A new hook or any changed definition gets a new review state, so a quiet skip often means the trust step is incomplete rather than the script being broken.
Open /hooks before debugging the handler. Review where the hook came from, the event it uses, its matcher and the command it will run. Trust it only when those parts match what you intended. If the source is no longer needed, disable the individual hook there instead of weakening every policy.
The project layer has its own boundary. A repository hook is ignored when the project's .codex/ configuration is untrusted, while user and system hooks can still load. This keeps a cloned repository from silently adding a local command to your sessions.
Where Codex looks for hooks
Choose a user file when the hook should apply across projects. Choose a repository file when the rule belongs with one codebase.
Useful hook locations
Scope | JSON file | Inline configuration |
|---|---|---|
User | | |
Repository | | |
Source: OpenAI, Hooks, read 4 November 2026.
Codex loads matching hooks from all active layers. A higher-precedence layer does not replace lower-layer hooks. If one layer has both formats, Codex merges them and warns at startup. OpenAI recommends using one representation per layer. Repository hooks load only when the project .codex/ layer is trusted.
Set up your first hook in the CLI
Choose the hooks file, add the event and command, write the script, trust it in /hooks, then run Codex and check.

Step 1: Choose the hooks file
Create .codex/hooks.json in the repository. Use ~/.codex/hooks.json instead when the same hook should apply to every project.
You should see: a startup warning if the new hook still needs review.
In our example: the policy belongs to one repository, so it uses the repository file.
Step 2: Add the event and command
Save this complete file as .codex/hooks.json. It wraps OpenAI's documented PreToolUse block in the required top-level hooks object. The hook matches Bash and runs the policy script from the Git root.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",
"statusMessage": "Checking Bash command"
}
]
}
]
}
}A hook has 3 levels: the event, a matcher group and one or more handlers. Commands run from the session working directory, so resolving a repository script from the Git root keeps its path stable when Codex starts in a subdirectory.
You should see: Codex mark the new definition for review and skip it until trusted.
In our example: the matcher limits this policy to Bash calls.
Step 3: Write the policy script
Save the script in the worked example below as .codex/hooks/pre_tool_use_policy.py, which is the path the documented command runs. Every command hook receives one JSON object on standard input. For Bash, the tool name is Bash and the command is in tool_input.command. Exit with no output to allow the call. Return the documented deny object to block it.
You should see: the script receive the pending Bash command before the tool runs.
In our example: the script below rejects a command that contains rm -rf.
Step 4: Review and trust the hook
Start the Codex CLI and enter /hooks. Inspect the source and exact definition, then trust it. The command also lets you disable an individual non-managed hook.
You should see: the hook listed as trusted for its current definition.
In our example: trust the repository hook only after reading its command and script.
Step 5: Run Codex and check
First, test our script directly with this safe input:
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | python3 .codex/hooks/pre_tool_use_policy.pyYou should see: the deny object printed.
Then create an empty test folder named build. In Codex, ask it to run rm -rf build on that empty folder. Never use a real target.
You should see: the tool call blocked with the hook's reason.
In our example: the direct test proves the script returns a deny object before the same command reaches the tool.
Our example (illustrative).
Our example: refuse-rm-rf
Save this file as .codex/hooks/pre_tool_use_policy.py. The small policy script is ours, not OpenAI's. It uses only the documented input fields and deny shape.
import json
import re
import sys
payload = json.load(sys.stdin)
tool_name = payload.get("tool_name", "")
command = payload.get("tool_input", {}).get("command", "")
if tool_name == "Bash" and re.search(r"(^|\s)rm\s+-rf(\s|$)", command):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook."
}
}))The deny object follows this official shape:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook."
}
}Plain text on standard output is ignored for PreToolUse. You can also exit with code 2 and write the blocking reason to standard error. Exit code 0 with no output lets Codex continue.
Which events can you hook?
A matcher is a regular expression. An asterisk, an empty string or no matcher matches every occurrence of an event that supports matching.
Events and matcher behavior
Event | When it runs | Matcher filters |
|---|---|---|
| Before a supported local tool | Tool name |
| After a supported local tool | Tool name |
| When Codex requests permission | Tool name |
| When a session starts | Startup, resume, clear, compact or fork |
| Around compaction | Manual or auto |
| Before a prompt is submitted | Ignored |
| When a turn stops | Ignored |
Source: OpenAI, Hooks, read 4 November 2026.
Choose the event by timing. Use PreToolUse when the decision must happen before a local tool call. Use PostToolUse for review or logging after output exists. Use UserPromptSubmit when the policy is about the user's prompt, and use Stop when the session should continue after Codex would otherwise stop.
Then narrow the event only when its matcher is meaningful. Tool events match on tool name, so ^Bash$ avoids running a shell policy for other local tools. Compaction events match manual or auto. A matcher on UserPromptSubmit or Stop is ignored, so place the condition inside the handler instead.
The event list also tells you where not to put a policy. A review after the tool runs cannot reverse a file change or command side effect. A stop hook can ask the session to continue, but it does not act like a tool denial. Pick the point where the decision can still have the effect you want.
The same PreToolUse hook can be written inline in config.toml:
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py"'
timeout = 30
statusMessage = "Checking Bash command"Block or steer with other events
A UserPromptSubmit hook can block a prompt with this documented response:
{
"decision": "block",
"reason": "Ask for confirmation before doing that."
}A blocking Stop response makes Codex continue and uses its reason as a new prompt. PostToolUse runs too late to undo a tool's side effects. A background hook also cannot block, approve or rewrite the operation that started it.
Choose command or MCP tool handlers
Command handlers run a local process and receive their input as JSON on standard input. They are a practical fit for a repository script that already lives beside the code. An MCP tool handler calls a configured server tool instead. Prompt and agent handler types may parse, but the hooks page says they are skipped.
Use synchronous handlers for policy. Setting async to true starts a command hook in the background while work continues. That is useful for logging, but a background hook cannot block or change the operation. A command hook's timeout is measured in seconds; when omitted, most hooks use 600 seconds.
What hooks do not cover
Shell commands use the local tool hook path and match as Bash. Hosted tools such as WebSearch do not use that path, so PreToolUse and PostToolUse do not run for them. OpenAI describes tool hooks as a guardrail, not a complete enforcement boundary.
Keep model-visible hook output concise. Codex limits each such message to roughly 2,500 tokens by default, and context from several hooks adds up. For more ways to split work safely, see Codex subagents. For repository settings beyond hooks, use the Codex CLI configuration guide.
A hook guards what Codex does in your repository. What AI engines say about your product is a separate check: Xtrusio's free check shows when ChatGPT and Gemini mention you, which competitors appear instead and what to work on next.
If your hook does not run
Start with trust, then check the active layer, feature setting, matcher and tool path.
- New or changed hook: open
/hooks, review the exact definition and trust it. - Repository hook missing: confirm the project
.codex/layer is trusted. - Hooks disabled: remove the setting below or set it to true.
- Matcher missed: match the tool name, or remove the matcher from an event that ignores it.
- Hosted tool: do not expect local tool hooks to intercept
WebSearch.
[features]
hooks = falseFor already-vetted one-off automation, --dangerously-bypass-hook-trust runs enabled hooks without stored trust for that invocation. The flag removes a safety check, so use it only when another process has already vetted the hook source.
Get your free AI visibility check
See when ChatGPT and Gemini mention you, which competitors appear instead and what to work on next with Xtrusio's free AI visibility check.
Sources
OpenAI and Xtrusio pages read 4 November 2026.
- Hooks, OpenAI
- Xtrusio home page, Xtrusio
Frequently asked questions
Are Codex hooks on by default?
Yes. Hooks are enabled by default. Set hooks = false under [features] in config.toml to turn them off.
Where do I put a Codex hook file?
Use ~/.codex/hooks.json for user-wide hooks or the repository's.codex/hooks.json for repository hooks. You can also define hooks inline in the matching config.toml.
Why does my new hook not run?
Codex skips new or changed non-managed hooks until you review and trust their exact definition in /hooks.
Can a hook block a shell command?
Yes. A synchronous PreToolUse hook can return a deny decision before a Bash call runs.
What happens when 2 hooks match one event?
All matching hooks run. Matching command hooks for the same event start concurrently.
Topics
- codex hooks
Xtrusio
AI visibility research
Xtrusio is the Perception Engine for B2B brands.
Free check
See how AI engines answer about you
Get your free AI visibility check
Keep reading
Technical SEO · Nov 4, 2026 · 7 min readClaude Code Agent Teams: How to Set Up and Use Them
Set up Claude Code agent teams, choose teams or subagents, control display modes, understand token costs and fix common problems.
Technical SEO · Nov 4, 2026 · 7 min readClaude Code /loop: How to Run a Prompt on a Repeat
Learn how Claude Code /loop repeats a prompt, how to choose fixed or dynamic timing, stop jobs, use loop.md and fix common scheduling issues.
Technical SEO · Nov 4, 2026 · 8 min read