Summary
When a preToolUse hook returns permissionDecision: "allow" for the exit_plan_mode tool, the built-in plan approval dialog still appears, requiring the user to manually click through it. This makes it impossible for hooks to fully automate plan approval, even though "deny" already fully short-circuits tool execution.
Current behavior
preToolUse hook fires for exit_plan_mode
- Hook returns
{ "permissionDecision": "allow" } -- this only answers "yes, this tool may run"
- The tool executes and calls
requestExitPlanMode() internally
- The plan approval dialog (
exit_plan_mode.requested) appears anyway
- User must manually approve a second time
"deny" works perfectly -- the tool never executes, no dialog appears. But "allow" is effectively a no-op for tools that have their own built-in interaction dialogs.
Use case
I build Plannotator, a visual plan review UI for agentic coding tools. My preToolUse hook intercepts exit_plan_mode, opens a rich review interface, collects the user's decision, and returns "allow" or "deny" with feedback. The deny path works great. But on approval, users still have to click through the CLI's plan dialog -- making the approval flow feel redundant.
Hook config:
{
"hooks": {
"preToolUse": [
{
"type": "command",
"bash": "$HOME/.local/bin/plannotator copilot-plan",
"timeoutSec": 345600
}
]
}
}
Proposed solution
Allow preToolUse hooks returning "allow" for tools with built-in dialogs (like exit_plan_mode) to supply the dialog response, skipping the dialog entirely. For example:
{
"permissionDecision": "allow",
"exitPlanModeResponse": {
"approved": true,
"selectedAction": "interactive",
"autoApproveEdits": false
}
}
Alternatively, any of these would also work:
- A dedicated
preExitPlanMode hook type in QueryHooks with ExitPlanModeResponse as its output type
- A general
preInteraction hook that fires before any blocking dialog on the InteractionManager
- Treating
preToolUse "allow" on exit_plan_mode as implicit plan approval (using defaults for selectedAction, etc.)
Why this matters
The current behavior is asymmetric: "deny" gives hooks full control, but "allow" still forces a manual step. Extensions that implement their own review UI (with richer context, annotations, sharing, etc.) cannot fully replace the built-in dialog on the approval path. This limits the extensibility that the hook system otherwise enables well.
For provenance purposes, this issue was AI assisted.
- not spam though, also the person who authored this issue.
Summary
When a
preToolUsehook returnspermissionDecision: "allow"for theexit_plan_modetool, the built-in plan approval dialog still appears, requiring the user to manually click through it. This makes it impossible for hooks to fully automate plan approval, even though"deny"already fully short-circuits tool execution.Current behavior
preToolUsehook fires forexit_plan_mode{ "permissionDecision": "allow" }-- this only answers "yes, this tool may run"requestExitPlanMode()internallyexit_plan_mode.requested) appears anyway"deny"works perfectly -- the tool never executes, no dialog appears. But"allow"is effectively a no-op for tools that have their own built-in interaction dialogs.Use case
I build Plannotator, a visual plan review UI for agentic coding tools. My
preToolUsehook interceptsexit_plan_mode, opens a rich review interface, collects the user's decision, and returns"allow"or"deny"with feedback. The deny path works great. But on approval, users still have to click through the CLI's plan dialog -- making the approval flow feel redundant.Hook config:
{ "hooks": { "preToolUse": [ { "type": "command", "bash": "$HOME/.local/bin/plannotator copilot-plan", "timeoutSec": 345600 } ] } }Proposed solution
Allow
preToolUsehooks returning"allow"for tools with built-in dialogs (likeexit_plan_mode) to supply the dialog response, skipping the dialog entirely. For example:{ "permissionDecision": "allow", "exitPlanModeResponse": { "approved": true, "selectedAction": "interactive", "autoApproveEdits": false } }Alternatively, any of these would also work:
preExitPlanModehook type inQueryHookswithExitPlanModeResponseas its output typepreInteractionhook that fires before any blocking dialog on theInteractionManagerpreToolUse"allow"onexit_plan_modeas implicit plan approval (using defaults forselectedAction, etc.)Why this matters
The current behavior is asymmetric:
"deny"gives hooks full control, but"allow"still forces a manual step. Extensions that implement their own review UI (with richer context, annotations, sharing, etc.) cannot fully replace the built-in dialog on the approval path. This limits the extensibility that the hook system otherwise enables well.For provenance purposes, this issue was AI assisted.