PreToolUse(Bash) hook returning permissionDecision: "defer" causes "[Tool result missing due to internal error]"
Summary
A PreToolUse hook scoped to Bash that returns {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "defer"}} causes every matching Bash tool call to fail with [Tool result missing due to internal error]. The command never executes. Non-deferred decisions (allow, deny, ask) work correctly.
This worked previously and broke without any change to the hook. It persists across full machine restart and extension version downgrade, which points at a harness-side regression in resolving the defer decision rather than a hook or environment issue.
Environment
- Claude Code VS Code extension
2.1.159(also reproduced on the prior version via downgrade) - macOS (Darwin arm64)
- Hook configured in
~/.claude/settings.jsonunderhooks.PreToolUse, matcherBash defaultMode: "auto"
Repro
- Add a PreToolUse hook for
Bashthat prints{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "defer"}}and exits 0. - Have the agent run any Bash command (e.g.
echo alive). - Tool call fails immediately with
[Tool result missing due to internal error]. The shell never spawns.
Evidence
Extension log (Anthropic.claude-code output channel) at the moment of failure:
[DEBUG] Hook PreToolUse:Bash (PreToolUse) success: {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "defer"}}
[DEBUG] Hook PreToolUse (.../git-guard.py) returned permissionDecision: defer
[DEBUG] Hook result has permissionBehavior=defer
The hook is parsed and validated successfully ("Successfully parsed and validated hook JSON output"), then permissionBehavior=defer is recorded -- but resolution dead-ends instead of falling through to the normal permission system.
Workaround
Disabling the hook (or changing defer -> allow) restores Bash. Neither is acceptable long-term: defer is documented as "fall through to the normal permission system," and allow bypasses the user permission allow-list. The hook in question is a git-safety guardrail, so disabling it removes deployment protections.
Expected
permissionDecision: "defer" should fall through to the configured permission mode / allow-list, exactly as documented, and the command should run (or prompt) accordingly.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗