Safeguard
Developer Tools•

Claude Code hooks: how exit code 2 blocks an action

Hooks run your script at fixed points in Claude Code's lifecycle. How PreToolUse hooks block with exit code 2, how decisions combine, and what exit 0 means.

Safeguard Security Team
3 min read

A Claude Code hook is a shell command that runs at a set point in the session, such as before a tool call. A PreToolUse hook that exits with code 2 blocks the action and sends its stderr message back. Exit 0 does not approve anything, and other exit codes usually let the action proceed.

Why hooks matter

Anthropic's hooks guide frames hooks as deterministic control: certain actions always happen, rather than depending on the model to choose to run them. The memory documentation makes the contrast sharp. CLAUDE.md is context. To block an action regardless of what Claude decides, use a PreToolUse hook.

Events you will use

The guide lists many events. The common ones:

  • SessionStart: when a session begins or resumes.
  • UserPromptSubmit: when a prompt is submitted, before Claude processes it.
  • PreToolUse: before a tool call executes, and it can block it.
  • PostToolUse: after a tool call succeeds, good for formatters.
  • Stop: when Claude finishes responding.

Hooks live in a settings file, so you can scope them to a user, a project or an organization.

Exit codes

Hooks talk to Claude Code through stdin, stdout, stderr and exit codes. Event data arrives as JSON on stdin, including the session id and working directory. A blocking script looks like this:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
if echo "$cmd" | grep -q 'rm -rf'; then
  echo "Blocked: destructive delete" >&2
  exit 2
fi
exit 0

Per the guide:

  • Exit 0: no objection. For PreToolUse this does not approve the call. The normal permission flow still applies.
  • Exit 2: Claude Code blocks the action, and the stderr reason goes to Claude as feedback for some events and to you for others.
  • Any other code: for most events the action proceeds and the failure is reported as a non-blocking error.

That last rule is a trap. A script that crashes does not block anything unless you configured the hook as one whose failure blocks the action.

Several hooks on one event

When multiple hooks match, every one runs to completion before results merge. One hook returning deny does not stop sibling hooks from executing, so do not rely on a deny to suppress side effects in another hook. For PreToolUse decisions, the most restrictive answer wins, in the order deny, defer, ask, allow.

Limits to remember

Shell commands can also change files, so a hook matching only edit tools will not see everything. The guide suggests a Stop hook that scans the working tree once per turn if you need every change for compliance or audit.

Hooks run with your user permissions, so review them like any script. Organizations can audit or block settings changes during a session with ConfigChange hooks.

Quick check

Does a PreToolUse hook that exits 0 approve the tool call?

No. The normal permission flow still applies.

What if my blocking script crashes with exit code 1?

For most events the action proceeds as a non-blocking error. Use exit 2 to block.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.