[BUG] Hook PermissionRequest contract: setMode:'bypassPermissions' silently dropped in 2.1.110+, breaking a documented extension path
Summary
The PermissionRequest hook response schema documents setMode with "bypassPermissions" as a supported value. Since Claude Code 2.1.110, a hook response that returns behavior: "allow" combined with updatedPermissions: [{type: "setMode", mode: "bypassPermissions", ...}] has its setMode operation silently dropped: the tool call is allowed, but the session permission mode never changes and the hook receives no error feedback.
This breaks a documented hook contract that every third-party hook-based integration is built against.
Why this matters for the hook platform
Hooks are positioned as Claude Code's sanctioned extension mechanism. The contract between Anthropic and the ecosystem is: if the docs list an operation and value as supported, a hook returning it will either succeed or surface an error. Silent drops violate both halves of that contract:
- Extensions relying on the documented API have no way to detect rejection and fall back gracefully
- The docs continue to advertise
bypassPermissionsas a validsetModevalue (https://code.claude.com/docs/en/hooks), creating a documentation / runtime mismatch - Only this one value is affected —
setMode: "acceptEdits","default","plan","dontAsk"all continue to work as documented in the same payload structure, so the mechanism itself is live
If bypassPermissions is intentionally policy-restricted in 2.1.110+ (per that release's changelog entry about disableBypassPermissionsMode), the hook API should:
- Return a structured error in the hook response channel, or
- Document the restriction in the PermissionRequest reference, or
- Both
Silent drop with no feedback is the worst of the three choices for an extension surface.
Distinct from #36168
#36168 tracks the VS Code extension UI dropdown bypass toggle. This report is specifically about the hook response protocol path, which is a different code path with different consumers (any hook-based integration, not just the VS Code extension).
Minimal reproduction
The repro uses only documented APIs — a shell script hook and a settings.json entry. No third-party products involved.
- Create
/tmp/bypass-test.sh, make executable:
#!/bin/bash
cat <<'EOF'
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{"type": "setMode", "mode": "bypassPermissions", "destination": "session"}
]
}
}
}
EOF
- Add to
~/.claude/settings.json:
{
"hooks": {
"PermissionRequest": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "/tmp/bypass-test.sh" }] }
]
}
}
- Launch
claude, ask it to runtouch /tmp/test-$(date +%s).txt. The prompt dismisses, the command runs. - Ask for a second write:
touch /tmp/test2-$(date +%s).txt.
Expected: step 4 runs without a prompt because the session was switched to bypassPermissions in step 3. Status line shows bypassPermissions after step 3.
Actual: step 4 prompts again. Status line still shows default. No error appears anywhere; the hook's JSON response was well-formed and accepted at the behavior: allow level.
Control: substituting acceptEdits works
Changing the mode value in the same hook payload from "bypassPermissions" to "acceptEdits" produces the documented behavior: status line switches, subsequent file edits and filesystem commands run without prompting. This rules out a systemic setMode-channel failure and narrows the defect to one specific value.
Version matrix
| Version | Hook setMode:bypassPermissions | Hook setMode:acceptEdits |
|----------|-------------------------------:|-------------------------:|
| 2.1.110 | Silently dropped | Works |
| 2.1.111 | Silently dropped | Works |
| 2.1.112 | Silently dropped | Works |
Environment
- Claude Code 2.1.112 (darwin-arm64, npm-installed)
- macOS 25.3.0
- Anthropic API provider (not Bedrock/Vertex/Foundry)
- No
disableBypassPermissionsModeset in user, project, or managed/remote settings — verified viagrepacross all settings file locations - No MDM managed-settings.json on disk
Suggested resolution
Either restore the pre-2.1.110 behavior of honoring setMode: "bypassPermissions" when no policy gates are set, OR surface structured rejection in the hook response channel so integrations can detect it and fall back. Silent drops should not be an outcome of a hook API call that the docs advertise as supported.
Related: #48865 (docs caveat on the same path, filed from the docs side).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗