Summary
A PreToolUse hook that returns permissionDecision: "defer" (documented as "use normal flow") permanently wedges the tool call when the session runs in bypassPermissions mode. The transcript logs hook_success → hook_additional_context → hook_deferred_tool, and then the tool never executes — no tool_result is ever written. The turn ends showing Stopped, and in other occurrences the user-visible message is "Tool execution was interrupted." The model never sees a result, so the work silently dies mid-turn.
Environment
- Claude Code 2.1.219 (Windows desktop app)
- Windows 11 Pro 10.0.26200
- Permission mode: bypassPermissions
- Model: claude-opus-5
- Hook: project-level
PreToolUse on matcher Edit|Write, powershell.exe script, exits 0
Hook output that triggers it
The hook is warn-only: it should let the tool run and inject a note into context. It printed (single line, exit code 0):
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"defer","additionalContext":"This path is inside lane 'x' owned by another session. ..."}}
Per https://code.claude.com/docs/en/hooks.md, "defer" is a valid permissionDecision value meaning the normal permission flow should apply. Under bypassPermissions, the normal flow would allow the tool — instead the call is parked and never resolves.
Transcript evidence (session .jsonl, paths sanitized)
The session's last entries — an Edit tool_use that never gets a result:
type=attachment hook_success PreToolUse:Edit toolUseID=toolu_01Rd8...
stdout: {"hookSpecificOutput":{"additionalContext":"This path is inside lane ...","permissionDecision":"defer","hookEventName":"PreToolUse"}}
type=attachment hook_additional_context PreToolUse:Edit toolUseID=toolu_01Rd8...
type=attachment hook_deferred_tool toolName=Edit toolUseID=toolu_01Rd8...
permissionMode: "bypassPermissions"
--- end of file: no tool_result, no further entries ---
The UI shows the edit group as "Stopped"; the user pressed nothing (no Esc, no new message).
Reproduction
- Session in
bypassPermissions mode on Windows.
- Register a PreToolUse hook (matcher
Edit|Write) whose script prints the JSON above and exits 0.
- Make Claude edit any file so the hook fires.
- The Edit is deferred and never executes; the turn dies.
Intermittent-looking in practice because our hook only emits defer on certain paths — every "random interruption" we chased for two days correlated 1:1 with the hook taking the defer branch.
Expected
"defer" should hand the call to the normal permission evaluation; under bypassPermissions the tool should simply run (with additionalContext injected). At minimum, an unresolved deferred call should fail loudly rather than silently ending the turn.
Workaround
Omit permissionDecision entirely and return only additionalContext (the docs' warn-only example). The tool then executes normally and the context is delivered — confirmed on the same machine/version immediately after the change, hooks otherwise identical.
🤖 Filed with Claude Code
Summary
A PreToolUse hook that returns
permissionDecision: "defer"(documented as "use normal flow") permanently wedges the tool call when the session runs inbypassPermissionsmode. The transcript logshook_success→hook_additional_context→hook_deferred_tool, and then the tool never executes — no tool_result is ever written. The turn ends showing Stopped, and in other occurrences the user-visible message is "Tool execution was interrupted." The model never sees a result, so the work silently dies mid-turn.Environment
PreToolUseon matcherEdit|Write,powershell.exescript, exits 0Hook output that triggers it
The hook is warn-only: it should let the tool run and inject a note into context. It printed (single line, exit code 0):
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"defer","additionalContext":"This path is inside lane 'x' owned by another session. ..."}}Per https://code.claude.com/docs/en/hooks.md,
"defer"is a validpermissionDecisionvalue meaning the normal permission flow should apply. UnderbypassPermissions, the normal flow would allow the tool — instead the call is parked and never resolves.Transcript evidence (session .jsonl, paths sanitized)
The session's last entries — an
Edittool_use that never gets a result:The UI shows the edit group as "Stopped"; the user pressed nothing (no Esc, no new message).
Reproduction
bypassPermissionsmode on Windows.Edit|Write) whose script prints the JSON above and exits 0.Intermittent-looking in practice because our hook only emits
deferon certain paths — every "random interruption" we chased for two days correlated 1:1 with the hook taking the defer branch.Expected
"defer"should hand the call to the normal permission evaluation; underbypassPermissionsthe tool should simply run (withadditionalContextinjected). At minimum, an unresolved deferred call should fail loudly rather than silently ending the turn.Workaround
Omit
permissionDecisionentirely and return onlyadditionalContext(the docs' warn-only example). The tool then executes normally and the context is delivered — confirmed on the same machine/version immediately after the change, hooks otherwise identical.🤖 Filed with Claude Code