Guide

Claude Code hooks explained

Hooks are how you attach deterministic rules to an AI coding agent instead of relying on the model's own judgment alone. Here is what the main hook events actually receive and return, and how they add up to a full approval gate.

What a hook is

A Claude Code hook is a shell command you register in settings.json (project-level or the global ~/.claude/settings.json) that Claude Code runs automatically at specific points in its own lifecycle. Claude Code writes a JSON payload to the hook's stdin describing what is happening, waits for the hook to finish, and reads a JSON response (or exit code) back to decide what to do next. The full field-by-field schema lives in Anthropic's own hooks reference and hooks guide, this page focuses on the events that matter for building a safety or approval layer.

PreToolUse: the actual gate

PreToolUse fires before a tool call executes, and it is the only hook that can stop one. Its stdin payload includes tool_name and tool_input, the exact arguments the agent is about to run. Instead of a flat allow/deny, its decision lives inside a hookSpecificOutput object and supports four outcomes: allow, deny, ask, or defer. It can also return updatedInput to rewrite the tool's arguments before they run, not just approve or block them outright. When more than one PreToolUse hook matches the same call, precedence runs deny > defer > ask > allow, the most cautious answer wins.

PostToolUse: after the fact

PostToolUse runs immediately after a tool has already executed successfully. Its payload includes both tool_input (what was sent) and tool_response (what came back), which makes it the right place for logging, auditing, or triggering a notification, but it cannot undo a call that already ran. If you need to stop something, it has to be PreToolUse.

Stop, and a real gotcha

Stop fires when an interactive session ends, useful for a final summary or notification. The gotcha worth knowing before you build on it: Stop does not fire for headless runs (claude -p). If you need to detect completion of a headless run, watch the "type": "result" line in --output-format stream-json output instead of assuming Stop will fire.

Other events worth knowing

  • SessionStart fires when a session begins, a common place to inject project context or run a dependency/security check.
  • UserPromptSubmit fires when you submit a prompt, before Claude processes it.
  • Notification fires on Claude Code's own built-in notifications, separate from anything a PreToolUse hook triggers itself.

A minimal PreToolUse gate, conceptually

Stripped to its core, a hook that blocks on a dangerous command looks like this:

# .claude/settings.json (excerpt)
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "/path/to/check-command.ps1" }]
      }
    ]
  }
}

The script reads the JSON payload from stdin, inspects tool_input.command, and writes a decision back:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Destructive command, confirm before running."
  }
}

From there it is a short step to a full remote approval system: instead of just asking locally, the hook can send that same payload to another device, block until a human responds, and return whatever decision comes back.

Two things that bite people building on this

  • A hook script reading stdin should decode it as UTF-8 explicitly. Reading raw console input with a platform's default encoding will silently corrupt any non-ASCII text (em dashes, curly quotes, accented characters) in tool arguments or file contents.
  • A hook's own internal timeouts need to stay well under the timeout declared for it in the hooks config. If the script's own network call or wait logic can take longer than the declared budget, the platform kills it before it finishes, even if the underlying action (like sending a notification) would have succeeded a second later.

Where ÆthelHook fits

ÆthelHook is a working example of exactly this pattern: a PreToolUse hook that forwards the tool call to a phone over an encrypted connection, blocks, and returns whatever decision comes back, for both Claude Code and Codex.

See how it works and download it.