Skip to main content

Hook Explorer

A hook is a command Claude Code or Codex runs at a set point in a session. It gets a JSON payload on stdin and can answer with JSON on stdout. Pick an event to see both shapes, then break a payload and watch the check catch it.

Hooks in

PreToolUse

When it fires
Fires before a tool runs.
What its matcher matches
The tool name.
Whether it can block
Can block or allow: permissionDecision takes allow, deny, ask, or defer, and updatedInput rewrites the tool’s input.

Codex has a PreToolUse event too.

  • Only in Claude Code: stdin scratchpad_dir, prompt_id, effort, mcp_server;stdout stopReason, terminalSequence.
  • Only in Codex: stdin model, turn_id.

What it receives on stdin

  • hook_event_name string required

    Always PreToolUse

  • tool_name string required
  • tool_input any JSON required

    Its shape depends on the tool.

  • tool_use_id string required
  • mcp_server object optional
    • name string required
    • source string required
Fields every event gets (9)
  • session_id string required
  • transcript_path string required
  • cwd string required
  • scratchpad_dir string optional
  • prompt_id string optional
  • permission_mode string optional
  • agent_id string optional
  • agent_type string optional
  • effort object optional
    • level string required

Sample stdin

{
  "session_id": "<session_id>",
  "transcript_path": "<transcript_path>",
  "cwd": "<cwd>",
  "hook_event_name": "PreToolUse",
  "tool_name": "<tool_name>",
  "tool_input": {},
  "tool_use_id": "<tool_use_id>"
}

Strings in angle brackets are placeholders, and tool_input is {} here. A real one depends on the tool.

What it can print on stdout

Fields every event can print (7)
  • continue boolean optional
  • suppressOutput boolean optional
  • stopReason string optional
  • decision string optional

    One of approve, block

  • reason string optional
  • systemMessage string optional
  • terminalSequence string optional
  • hookSpecificOutput object optional
    • hookEventName string required

      Always PreToolUse

    • permissionDecision string optional

      One of allow, deny, ask, defer

    • permissionDecisionReason string optional
    • updatedInput object optional
    • additionalContext string optional

hookSpecificOutput must include hookEventName, set to PreToolUse.

Sample stdout

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse"
  }
}

Check a payload

It starts as the sample. Delete a required field, change a value, or add a key, then check it against PreToolUse in Claude Code.

Check

Exit codes

Claude Code

  • 0 is success. stdout is parsed as JSON when it starts with { and ends with }. Plain text on stdout is added as context on UserPromptSubmit, UserPromptExpansion, SessionStart, and PostModelSwitch; otherwise it goes to the debug log.
  • 2 is a blocking error on events that can block. It blocks whatever the JSON says, with the message from the JSON reason if there is one and from stderr if not.
  • Any other code is a non-blocking error for most events, and JSON on stdout is still read. On WorktreeCreate and WorktreeRemove, any non-zero exit is a failure.

Codex

  • 0 with valid JSON is success.
  • 2 is a blocking decision, with the reason taken from stderr.
  • An unknown key anywhere in the JSON on stdout makes the hook run fail, so the hook fails closed.
  • A block needs a non-empty reason, and a PreToolUse allow needs updatedInput. The field lists here can’t show either rule.