fix(plan-tune): stop deferring AskUserQuestion in non-interactive sessions#2349
fix(plan-tune): stop deferring AskUserQuestion in non-interactive sessions#2349mitch-schneider-1 wants to merge 1 commit into
Conversation
…sions The AskUserQuestion PreToolUse hook emitted `permissionDecision: "defer"` on its pass-through path (no preference, no marker — the common case). `defer` is an undocumented print-mode-only value; the documented set is `allow | deny | ask`. Interactive Claude Code parses it, logs "defer is print-mode only", and ignores it — so the hook looks correct in a terminal. Non-interactive sessions (Cowork, the Agent SDK, `claude -p`) honor it and leave the tool_use unresolved with no result, which the agent receives as "[Tool result missing due to internal error]". Net effect: AskUserQuestion was 100% dead in non-interactive hosts and 100% fine in the terminal, which reads as flaky transport rather than a hook bug and is easy to misattribute to the platform. Fix: emit no `permissionDecision` on pass-through. Absence is the correct "no opinion" signal and is the only form safe in both modes. `additionalContext` (plan-tune memory injection) is preserved, and the `deny` paths — never-ask auto-decide and the Conductor prose redirect — are unchanged. Also corrects docs/spikes/claude-code-hook-mutation.md, which documented `defer` as the pass-through shape and is where the bug originated. Verified: 89 pass / 0 fail across the 7 AskUserQuestion hook test files, and AskUserQuestion confirmed working live in a Cowork session where it had previously returned no result. Co-Authored-By: Claude <noreply@anthropic.com>
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
Closing as a duplicate — #2325 already covers this fix and touches the same files. I missed it before opening; apologies for the noise. Leaving one note here in case it's useful to whoever picks #2325 up: the reason this reads as flaky transport rather than a hook bug is that Worth also fixing |
Summary
The
AskUserQuestionPreToolUsehook emitspermissionDecision: "defer"on its pass-through path — no preference set, no<gstack-qid:…>marker, i.e. the common case. This makesAskUserQuestionsilently fail in every non-interactive host while working perfectly in the terminal.Root cause
deferis an undocumented, print-mode-only value. The documented set isallow | deny | ask. From the Claude Code 2.1.216 binary:So:
claude -p)In the non-interactive case the agent receives
[Tool result missing due to internal error]and the question never reaches the user.The failure mode is nasty precisely because it's mode-split: 100% broken in one host, 100% fine in another. That reads as flaky transport, so it gets misattributed to the platform rather than the hook. It also defeats the existing
auq-error-fallback-hookmitigation, since a deferred tool never produces the error response that hook keys on.Fix
Emit no
permissionDecisionon pass-through. Absence is the correct "no opinion" signal and the only form that is safe in both modes.Unchanged:
additionalContext(plan-tune memory injection) still emitted when present.denypaths —never-askauto-decide and the Conductor prose redirect — are untouched.Also corrects
docs/spikes/claude-code-hook-mutation.md, which documenteddeferas the pass-through shape. That's where the bug originated, so leaving it would re-seed the regression.Testing
AskUserQuestionhook test files.defercontract; they now assert absence.AskUserQuestionconfirmed working in a Cowork session where it had consistently returned no result.skill-e2e-plan-tune-cathedraleval scenarios fail identically onmainand on this branch (pre-existing, unrelated — per the repo's E2E blame protocol).Health check — empty stdout is correct:
🤖 Generated with Claude Code