Back to Codex CLI Template
    8 hooks · 2 lifecycle points
    Updated September 2026

    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

    Every hook executes shell code on your machine inside the agent loop. Read each script before you register it, and start with the lower-risk, logging-only hooks before enabling ones that deny tool calls.

    Filter by lifecycle point

    Filter by category

    Showing 8 of 8 hooks

    Block dangerous shell commands

    before_tool_use
    Medium risk
    Safe Defaults
    P0
    Security
    Safe Defaults

    Deny 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" }'
    Source

    Block surprise package installs

    before_tool_use
    Medium risk
    Safe Defaults
    P0
    Safe Defaults
    Cost Optimization

    Deny 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" }'
    Source

    Protect .env and secrets

    before_tool_use
    Low risk
    Security
    P0
    Security
    Safe Defaults

    Deny 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" }'
    Source

    Scan writes for hardcoded secrets

    before_tool_use
    Medium risk
    Security
    P1
    Security

    Pattern-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" }'
    Source

    Block direct commits to main

    before_tool_use
    Medium risk
    Safe Defaults
    P1
    Workflow
    Safe Defaults

    Deny 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" }'
    Source

    Run typecheck after edit

    after_tool_use
    Medium risk
    Agent Quality
    P1
    Quality
    Workflow

    After 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 '{}'
    Source

    Run lint after edit

    after_tool_use
    Medium risk
    Agent Quality
    P1
    Quality

    After 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 '{}'
    Source

    Log every edit for Brain Harvests

    after_tool_use
    Low risk
    Learner Brain
    P2
    Learner Brain
    Quality

    Append 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 '{}'
    Source

    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.

    Official Documentation

    This template was last reviewed in September 2026 against the official Codex Hooks documentation. Spot something out of date? Let us know.