[BUG] PermissionRequest hook 'allow' for ExitPlanMode is ignored since v2.1.199 — plan-approval chooser still blocks
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Since v2.1.199, a PermissionRequest hook that returns decision.behavior: "allow" for ExitPlanMode is executed (its stdin payload is delivered and its stdout response is read) but the decision is discarded. The native plan-approval chooser ("Claude has written up a plan and is ready to execute. Would you like to proceed?") is still displayed and blocks the session until answered manually.
This breaks tools that implement out-of-band plan approval via hooks (e.g. browser-based plan review UIs): the human approves in the external UI, but the terminal still hangs on the interactive chooser.
Variants tested on 2.1.201 — all ignored:
PermissionRequestallow withupdatedPermissions({"type": "setMode", "mode": "acceptEdits", "destination": "session"})PermissionRequestbare allow (noupdatedPermissions)PreToolUsehook withpermissionDecision: "allow"
Notably, the deny path still works on 2.1.201: decision.behavior: "deny" with a message round-trips correctly — the model receives the feedback, revises the plan, and calls ExitPlanMode again. Only the allow path is broken, which narrows the regression to how an allow decision is applied to the plan-approval dialog.
What Should Happen?
v2.1.198 behavior: when the hook returns allow, the plan-approval chooser is skipped, any updatedPermissions (e.g. setMode: acceptEdits) are applied, and the session proceeds directly into implementation. This matches the documented "Auto-approve specific permission prompts" flow in the hooks guide, and @anthropic-ai/claude-agent-sdk 0.3.201 still exports the same PermissionRequestHookSpecificOutput contract, so the current behavior appears to be an unintentional regression. Nothing in the 2.1.199–2.1.201 changelog mentions a behavior change for plan approval via hooks.
Error Messages/Logs
# Hook stdin payload captured on 2.1.201 (hook demonstrably fires; shape identical to 2.1.198):
{
"session_id": "7942a346-27fc-4d06-b627-84708db96ae9",
"cwd": "/tmp/repro",
"permission_mode": "plan",
"hook_event_name": "PermissionRequest",
"tool_name": "ExitPlanMode",
"tool_input": {
"plan": "# Context\n\nUser requested a simple file be created...",
"planFilePath": "~/.claude/plans/write-a-file-named-noble-crescent.md"
}
}
# Hook stdout response (read by Claude Code, then ignored):
{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedPermissions":[{"type":"setMode","mode":"acceptEdits","destination":"session"}]}}}
Steps to Reproduce
- Create
approve-hook.sh(chmod +x):
``bash``
#!/bin/bash
cat > /dev/null
echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedPermissions":[{"type":"setMode","mode":"acceptEdits","destination":"session"}]}}}'
- Create
settings.json:
``json``
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{ "type": "command", "command": "/path/to/approve-hook.sh", "timeout": 600 }
]
}
]
}
}
- In an empty directory, run
claude --permission-mode plan --settings settings.json - Prompt:
Write a file named hello.txt containing exactly: hi - Claude writes the plan and calls
ExitPlanMode
Expected (observed on 2.1.198): chooser is skipped, session switches to accept-edits, hello.txt is created.
Actual (observed on 2.1.199 and 2.1.201): the chooser is displayed and blocks; hello.txt is never created. Capturing the hook's stdin confirms the hook ran and answered allow.
Version bracketing (same repro, driven programmatically against the TUI):
| Version | Result |
|---|---|
| 2.1.198 | allow honored — chooser skipped, plan implemented |
| 2.1.199 | allow ignored — chooser blocks |
| 2.1.201 | allow ignored — chooser blocks |
Claude Model
Other: reproduced with Haiku 4.5; the behavior is model-independent (also observed with Opus-class models).
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.198
Claude Code Version
2.1.201 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other: reproduced both in the desktop app and headlessly (expect/xterm-256color driving the TUI), so terminal-independent.
Additional Information
Related but distinct existing issues: #71061 (showClearContextOnPlanAccept not honored on hook approval — the hook-allow bypass itself still worked there), #50660 (PreToolUse deny ignored on ExitPlanMode, older). This report is specifically about the allow path regressing between 2.1.198 → 2.1.199 while deny continues to work.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗