Codex Hooks Library
Codex hooks (CLI 0.76+) run deterministic shell actions around tool use — lint after edits, log diffs, enforce checks. In Learner Brain terms, they are the enforcement layer: a rule the agent keeps breaking graduates into a hook, and the correction never reaches you again. Every hook below is copy-ready.
These hooks apply to both Codex CLI and Codex for VS Code — they share the same engine and ~/.codex configuration.
The lifecycle points
before_tool_useRuns before a tool call — allow or deny it. The strongest enforcement point.after_tool_useRuns after a tool call — validate the result, log the diff, or feed issues back into the loop.Codex hooks fire around tool use. Event keys follow Codex's config conventions and have evolved across releases — verify the exact keys for your CLI version against the official hooks docs.
Hooks run real commands
Filter by lifecycle point
Filter by category
Showing 8 of 8 hooks
Block dangerous shell commands
before_tool_useDeny destructive commands — rm -rf, sudo, force-push, pipe-to-shell, and database drops — before they run.
When to use it
Give every Codex session a hard floor. This is the single highest-value guardrail: it converts your most important safety rules from suggestions into guarantees.
Notes
Deny reasons are fed back to Codex, so it learns what to do instead. Extend the pattern list to match your stack (e.g. add terraform destroy for infra repos).
Save as .codex/hooks/block-dangerous-bash.sh, make it executable, and register it under before_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
COMMAND="$(echo "$INPUT" | jq -r '.tool_input.command // ""')"
if echo "$COMMAND" | grep -Eiq '(rm\s+-rf\s+(/|~|\$HOME)|sudo\s|git\s+push\s+.*--force|curl[^|]*\|\s*(ba)?sh|wget[^|]*\|\s*(ba)?sh|DROP\s+(TABLE|DATABASE)|TRUNCATE\s+TABLE|mkfs|dd\s+if=.*of=/dev/)'; then
jq -n --arg msg "Blocked destructive command: $COMMAND. Explain what you are trying to do and propose a safer alternative." \
'{ "decision": "deny", "reason": $msg }'
exit 0
fi
jq -n '{ "decision": "allow" }'Block surprise package installs
before_tool_useDeny package install commands and ask Codex to justify the dependency before proceeding.
When to use it
Prevent Codex from adding dependencies without approval — keeps projects lean and avoids supply-chain surprises.
Notes
Good for keeping projects lean and preventing unnecessary dependency bloat. Pair with a stack-conventions rule that lists approved packages.
Save as .codex/hooks/block-package-installs.sh, make it executable, and register it under before_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
COMMAND="$(echo "$INPUT" | jq -r '.tool_input.command // ""')"
if echo "$COMMAND" | grep -Eiq '(npm install|npm i |pnpm add|yarn add|bun add|pip install|uv add|composer require|cargo add|go get)'; then
jq -n --arg msg "Package install blocked for review: $COMMAND. Do not install packages automatically. Explain why the dependency is needed and ask for approval." \
'{ "decision": "deny", "reason": $msg }'
exit 0
fi
jq -n '{ "decision": "allow" }'Protect .env and secrets
before_tool_useDeny any attempt to edit or create .env files, secret directories, or credential files.
When to use it
Enforce the 'never touch secrets' rule deterministically. The agent can read config; it cannot write credentials.
Notes
Reads are still allowed — only writes are blocked. Extend the path list with wherever your secrets live (e.g. k8s-secrets/, .vault/).
Save as .codex/hooks/protect-env-files.sh, make it executable, and register it under before_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')"
if echo "$FILE_PATH" | grep -Eiq '(^|/)(\.env|\.env\.|secrets?/|credentials|\.pem$|\.key$|id_rsa)'; then
jq -n --arg msg "Blocked write to protected path: $FILE_PATH. Secrets and environment files are managed manually. Ask the user instead." \
'{ "decision": "deny", "reason": $msg }'
exit 0
fi
jq -n '{ "decision": "allow" }'Scan writes for hardcoded secrets
before_tool_usePattern-match the incoming edit content for API keys and tokens, and block the write if one is found.
When to use it
Catch the classic 'paste the API key into source' mistake before it ever hits disk.
Notes
Regexes cover common key formats (sk-, AKIA, ghp_, xox, JWTs). Expect occasional false positives; tune the patterns per stack.
Save as .codex/hooks/prevent-secrets-in-code.sh, make it executable, and register it under before_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
CONTENT="$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""')"
if echo "$CONTENT" | grep -Eq '(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{10,}|eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,})'; then
jq -n --arg msg "Possible hardcoded secret detected in the edit. Block the write, show the offending line, and propose moving the value to an environment variable." \
'{ "decision": "deny", "reason": $msg }'
exit 0
fi
jq -n '{ "decision": "allow" }'Block direct commits to main
before_tool_useDeny commits and pushes that target main or master — force feature branches and pull requests.
When to use it
Enforce your branching rule when the agent keeps committing straight to main despite the rule in AGENTS.md.
Notes
This is the canonical 'rule graduates to a hook' example: the rule lived in AGENTS.md, kept being violated, and became deterministic. Adjust branch names to your convention.
Save as .codex/hooks/block-commit-to-main.sh, make it executable, and register it under before_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
COMMAND="$(echo "$INPUT" | jq -r '.tool_input.command // ""')"
if echo "$COMMAND" | grep -Eq '(git\s+(commit|push)).*(main|master)|git\s+push\s+(origin\s+)?(main|master)(\s|$)'; then
jq -n --arg msg "Direct commits/pushes to main are blocked. Create a feature branch, commit there, and open a pull request." \
'{ "decision": "deny", "reason": $msg }'
exit 0
fi
jq -n '{ "decision": "allow" }'Run typecheck after edit
after_tool_useAfter edits to TS/TSX files, run the project's typecheck script and feed errors back to Codex.
When to use it
Catch type errors inside the agent loop instead of after it claims the task is done.
Notes
Can be slow on large codebases — enable when typecheck is fast enough for interactive coding. Feed the output back so Codex fixes its own mistakes immediately.
Save as .codex/hooks/typecheck-after-edit.sh, make it executable, and register it under after_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')"
# Only care about TypeScript files
echo "$FILE_PATH" | grep -Eq '\.(ts|tsx)$' || { echo '{}'; exit 0; }
# Only run when the project has a typecheck script
[ -f package.json ] && jq -e '.scripts["typecheck"]' package.json >/dev/null 2>&1 || { echo '{}'; exit 0; }
if ! npm run typecheck >/tmp/codex-typecheck.log 2>&1; then
REASON="Typecheck failed after editing $FILE_PATH. Fix the errors before continuing:\n$(tail -n 20 /tmp/codex-typecheck.log)"
jq -n --arg msg "$REASON" '{ "decision": "block", "reason": $msg }'
exit 0
fi
jq -n '{}'Run lint after edit
after_tool_useAfter edits to JS/TS files, run the project's lint script when it exists.
When to use it
Enforce your coding-standards rule deterministically — the agent loop fixes lint issues as it works instead of leaving them for you.
Notes
Noisy on projects with slow linters. Prefer lint-staged or targeted-file linting if your lint script sweeps the whole repo.
Save as .codex/hooks/lint-after-edit.sh, make it executable, and register it under after_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')"
echo "$FILE_PATH" | grep -Eq '\.(js|jsx|ts|tsx)$' || { echo '{}'; exit 0; }
[ -f package.json ] && jq -e '.scripts["lint"]' package.json >/dev/null 2>&1 || { echo '{}'; exit 0; }
if ! npm run lint >/tmp/codex-lint.log 2>&1; then
REASON="Lint failed after editing $FILE_PATH. Fix the reported issues before continuing:\n$(tail -n 20 /tmp/codex-lint.log)"
jq -n --arg msg "$REASON" '{ "decision": "block", "reason": $msg }'
exit 0
fi
jq -n '{}'Log every edit for Brain Harvests
after_tool_useAppend each edited file path and diff summary to a dated log, so Brain Harvests scan real history instead of memory.
When to use it
Build the raw material for Brain Harvests automatically. At the end of a sprint, run the Harvest prompt over the log to extract rules, skills, and error solutions.
Notes
Writes to .codex/friction-log/YYYY-MM-DD.log. Commit the log if the repo is private and you want upgrade history; gitignore it if paths are sensitive.
Save as .codex/hooks/log-diffs-after-edit.sh, make it executable, and register it under after_tool_use in your Codex configuration.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')"
LOG_DIR="${CODEX_PROJECT_DIR:-.}/.codex/friction-log"
if [ -n "$FILE_PATH" ]; then
mkdir -p "$LOG_DIR"
{
echo "--- $(date +%FT%T) $FILE_PATH"
git diff -- "$FILE_PATH" 2>/dev/null | head -n 40
} >> "$LOG_DIR/$(date +%F).log"
fi
jq -n '{}'Registering hooks
Codex configuration is TOML — hooks live alongside the rest of your setup in ~/.codex/config.toml (global) or your project's .codex/config.toml. A representative registration:
# ~/.codex/config.toml
[hooks]
before_tool_use = [".codex/hooks/block-dangerous-bash.sh"]
after_tool_use = [".codex/hooks/typecheck-after-edit.sh", ".codex/hooks/log-diffs-after-edit.sh"]Event keys and the exact config shape have evolved across Codex releases — treat the official hooks documentation as the source of truth for your CLI version. Make scripts executable with chmod +x .codex/hooks/*.sh. Scripts read a JSON payload from stdin and need jq installed.
Known limits & gotchas — Codex Hooks
Real-world quirks that bite if you don't know about them. Not deal-breakers — just things to design around.
- Event keys evolve between releases — Codex moves fast — hook configuration shipped in 0.76+ and the exact keys have changed across versions. Pin your CLI version and verify registration against the official docs after upgrading.
- Hooks fire around tool use — Codex hooks run before/after tool calls, not on session start or prompt submit. Standing context still belongs in AGENTS.md; hooks are for deterministic checks.
- Sandbox and approvals interplay — Codex already gates commands through its sandbox and approval modes. Hooks add a second layer — don't duplicate what your approval policy already enforces, and don't fight it.
- jq and shell tools must be installed — These scripts assume jq, grep, and git are available. On minimal environments, install them first or the hook silently no-ops.
Universal Learner Brain Guides
These guides apply to all platforms — use your .codex/hooks/ path wherever they reference rule/skill locations.
Official Documentation
This template was last reviewed in September 2026 against the official Codex Hooks documentation. Spot something out of date? Let us know.