[BUG] Hook PermissionRequest contract: setMode:'bypassPermissions' silently dropped in 2.1.110+, breaking a documented extension path

Resolved 💬 2 comments Opened Apr 16, 2026 by edwluo Closed May 25, 2026

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 bypassPermissions as a valid setMode value (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:

  1. Return a structured error in the hook response channel, or
  2. Document the restriction in the PermissionRequest reference, or
  3. 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.

  1. 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
  1. Add to ~/.claude/settings.json:
{
  "hooks": {
    "PermissionRequest": [
      { "matcher": "*", "hooks": [{ "type": "command", "command": "/tmp/bypass-test.sh" }] }
    ]
  }
}
  1. Launch claude, ask it to run touch /tmp/test-$(date +%s).txt. The prompt dismisses, the command runs.
  2. 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 disableBypassPermissionsMode set in user, project, or managed/remote settings — verified via grep across 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).

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗