Claude Code Hooks: Which Event Actually Blocks a Tool Call?
Claude Code's PreToolUse hook can inspect and deny a matching tool call before it runs, using hookSpecificOutput.permissionDecision. PermissionRequest can also deny a call when Claude Code asks for permission or would otherwise auto-deny a call that cannot prompt; it is not a general gate for every tool invocation. PostToolUse fires after the tool succeeds and can change what Claude sees, but it cannot undo the action. For a policy that must cover every matching call, use PreToolUse and test its failure behavior.
AI-assisted editorial: researched and drafted with AI using the primary sources linked below. Examples are illustrative; they are not measured product results. Editorial policy.
Key takeaways
- PreToolUse can gate every matching tool call before execution via hookSpecificOutput.permissionDecision. PermissionRequest can deny calls in the permission-decision path, but it does not run for routinely allowed calls.
- PostToolUse fires after the tool has already completed successfully; it can replace the output Claude sees with updatedToolOutput, but it cannot stop the action from having happened.
- A PreToolUse hook that times out can now be overridden by another hook's explicit deny (as of v2.1.210); before that version, a timeout stalled an unattended session waiting for human input.
PreToolUse gates, PostToolUse reacts
Claude Code's hook system gives each lifecycle event a different amount of power over a tool call. PreToolUse runs before every matching tool invocation and can deny it through hookSpecificOutput.permissionDecision and hookSpecificOutput.permissionDecisionReason. The older top-level decision and reason fields are deprecated for this event. PermissionRequest is another before-execution decision point: it can deny when Claude Code asks for permission or would otherwise auto-deny a call that cannot prompt. Routinely allowed calls do not reach that hook. That distinction matters when designing a policy meant to cover every matching invocation.
PostToolUse sits on the other side of execution. It runs immediately after a tool completes successfully, which means whatever the tool did — the file write, the API call, the shell command — has already taken effect by the time the hook sees it. updatedToolOutput can replace the result Claude sees, but it does not erase the original result from telemetry or reverse the action. It cannot put the file back, cancel the API call, or reverse the command. Choose it for reacting to a completed call, not for preventing one.
Sources: Hooks reference - Claude Code Docs.
Illustrative example: protecting a credentials file
Say a bot builder wants to stop Claude Code from writing to a .env file during an unattended coding session. A PreToolUse policy can match the Write and Edit tools, inspect the proposed file path before the write, and return a deny through hookSpecificOutput.permissionDecision. A PermissionRequest hook could deny a write in the permission-decision path too, but it would not inspect a routinely allowed write. If the team instead wires the check into PostToolUse, the hook will see that Claude wrote to .env only after the write has happened. It can flag the event for review, not prevent it.
The docs draw this same line for a related use case: redaction. For redaction or transformation use cases, the guidance is to intercept at PreToolUse for outbound tool inputs and PostToolUse for inbound tool results. That split matters because the two directions carry different risk. An outbound input you want to stop (a command, a file path, a destination address) needs to be caught before it leaves, which is PreToolUse's job. An inbound result you want to clean up (a tool response containing a secret you don't want Claude to see verbatim) is naturally a PostToolUse job, because the tool has already run and you're only shaping what Claude reads next, not what happened in the world.
Sources: Hooks reference - Claude Code Docs.
Failure path: what happens when a PreToolUse hook times out
A gating hook that never responds is its own failure mode, and Claude Code's handling of it changed recently. Before v2.1.210, Claude Code reported the timeout to Claude as a user rejection, which made unattended sessions stop and wait for input — meaning a slow or hung PreToolUse hook didn't just fail safe or fail open, it silently froze an automated run until a person noticed and intervened. For any bot running on a schedule or inside a pipeline with no one watching, that's the same practical outcome as a crash: the routine goes quiet.
Current behavior changes what happens when hooks disagree or stall. If another PreToolUse hook returned an explicit deny, Claude receives that denial instead of the timeout error, so a slow hook no longer necessarily blocks a decision that a different, faster hook already made. On the reactive side, the contract is simpler: for PostToolUse and PostToolUseFailure, Claude Code keeps the tool result and the turn continues regardless of what the hook does, which reinforces that these two events were never designed to be a safety gate — they're observation points, and the run proceeds whether or not they succeed.
Sources: Intercept and control agent behavior with hooks - Claude Code Docs.
A checklist for picking the event
Before wiring a hook into a bot's Claude Code setup, ask whether the policy must cover every matching call or only the permission-decision path. For the general before-execution gate, use PreToolUse and hookSpecificOutput.permissionDecision rather than its deprecated top-level decision field. PermissionRequest can allow or deny when Claude Code asks for permission or would otherwise auto-deny a call that cannot prompt; it is not a substitute for a policy that must also cover routinely allowed calls. For responding after execution — logging, reformatting or shaping the result Claude reads — use PostToolUse or PostToolUseFailure, knowing that neither can roll back the tool call.
It's also worth checking the Claude Code version a bot is running against before assuming the newer timeout-override behavior applies. The change that lets an explicit deny from one PreToolUse hook override another hook's timeout landed at v2.1.210; a bot pinned to an older build will still see a timed-out gating hook treated as a user rejection that stalls the session. For any unattended routine, that's worth testing deliberately — run the gating hook with an artificial delay and confirm what the session does, rather than assuming the documented behavior matches the installed version.
Sources: Intercept and control agent behavior with hooks - Claude Code Docs.
Primary sources
Sources checked 2026-10-16. Standards and product documentation can change; follow the linked version when implementing.
- Hooks reference - Claude Code Docs — Anthropic (Claude Code Docs)
- Intercept and control agent behavior with hooks - Claude Code Docs — Anthropic (Claude Code Docs)
BotBento is in development. Suggest a correction.