Hooks let Codex run local commands at defined lifecycle points, giving a team a mechanical guardrail around prompts, tool calls, or session cleanup. A hook should stay small and auditable because Codex can load it from user, project, plugin, or managed configuration.

The simplest starting point is a user-level ~/.codex/hooks.json file that points to an executable script under ~/.codex/hooks. Codex also accepts inline [hooks] tables in config.toml, but keeping hook wiring in hooks.json separates lifecycle commands from general settings.

Non-managed command hooks require review and trust before they run, and the Codex CLI exposes that review flow through /hooks. Multiple matching hooks can run from different sources, and matching command hooks for the same event can start concurrently, so each hook should enforce its own safety checks instead of relying on another hook to run first.

Steps to add a Codex command hook:

  1. Create the user hook directory.
    $ mkdir -p ~/.codex/hooks

    Use .codex/hooks.json only when the hook belongs to one trusted repository. Codex ignores project-local hooks in untrusted projects, while user-level hooks remain independent of project trust.

  2. Save a harmless startup hook script as ~/.codex/hooks/startup.sh.
    startup.sh
    #!/bin/sh
    printf 'SessionStart startup\n' >> "$HOME/.codex/hook-events.log"

    Command handlers are the hook handler type that runs in current Codex releases. Keep the script deterministic and avoid prompting for input.

  3. Restrict the script so only the current user can run or edit it.
    $ chmod 700 \
      ~/.codex/hooks/startup.sh
  4. Add a SessionStart hook definition.
    ~/.codex/hooks.json
    {
      "hooks": {
        "SessionStart": [
          {
            "matcher": "startup",
            "hooks": [
              {
                "type": "command",
                "command": "~/.codex/hooks/startup.sh",
                "statusMessage": "Recording session start",
                "timeout": 10
              }
            ]
          }
        ]
      }
    }

    The matcher value limits this hook to new startup sessions. Omit matcher, set it to an empty string, or use * only when the hook should match every supported occurrence of that event.

  5. Validate the hook file syntax.
    $ python3 -m json.tool \
      ~/.codex/hooks.json
    {
        "hooks": {
            "SessionStart": [
                {
                    "matcher": "startup",
                    "hooks": [
                        {
                            "type": "command",
                            "command": "~/.codex/hooks/startup.sh",
                            "statusMessage": "Recording session start",
                            "timeout": 10
                        }
                    ]
                }
            ]
        }
    }
  6. Confirm the hook feature is enabled.
    $ codex features list
    ##### snipped #####
    hooks                                stable             true
    ##### snipped #####

    If hooks is false, remove the local feature override or set hooks = true under [features] in the active config.toml layer.

  7. Start the Codex CLI so it loads the hook source.
    $ codex
  8. Open the hook review browser in the Codex CLI.
    /hooks

    The hook should appear from ~/.codex/hooks.json as a non-managed command hook. Review the command path, event, matcher, and timeout before trusting it.

  9. Trust the hook after reviewing the exact command.

    Do not use --dangerously-bypass-hook-trust for normal interactive work. That flag skips persisted hook trust for one invocation and belongs only in automation that vets hook sources separately.

  10. Start a new Codex session after the hook is trusted.
    $ codex
  11. Check the startup hook log.
    $ cat ~/.codex/hook-events.log
    SessionStart startup

    If the log is empty, reopen /hooks and confirm the hook is trusted, enabled, and still matches the startup source.