fix(hooks): don't emit invalid permissionDecision 'defer' — it swallows the AskUserQuestion widget#2325
Conversation
|
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 |
|
/trunk merge |
|
An error occurred while submitting your PR to the queue: |
…ws AskUserQuestion
The PreToolUse question-preference-hook returned
`{"permissionDecision":"defer"}` to mean "no opinion, let the permission
flow continue". That value is not in Claude Code's PreToolUse schema, which
accepts only `allow` / `deny` / `ask`.
As of Claude Code 2.1.14 the unrecognized value is interpreted as "defer the
tool call": the call is dropped and recorded as a `hook_deferred_tool`
attachment. For AskUserQuestion this means the question widget never renders
— the user sees nothing at all and the agent is left waiting. Observed in a
real transcript: hook_success with `"permissionDecision":"defer"`, then
`hook_deferred_tool`, then the user typing "questions are not loading".
Because the defer path is the default for every non-auto-decidable question
(no preference set, marker missing, always-ask, one-way door safety
override), this broke AskUserQuestion for essentially all users, not just
those with plan-tune preferences configured.
Fix: omit `permissionDecision` entirely in defer(). That is the correct
"no opinion" signal — normal permission flow continues and
`additionalContext` (the memory-cache injection) is still delivered.
The deny paths (never-ask auto-decide, Conductor prose redirect) are
untouched and still enforce.
Tests updated to assert `permissionDecision` is undefined on the defer path
rather than the string 'defer'. 51 pass / 0 fail across
question-preference-hook, memory-cache-injection and gstack-question-log.
ade59ab to
2faf6ae
Compare
|
Heads up for maintainers: I'm an outside contributor pushing from a fork, so I can't submit this to the Trunk queue myself — my CI also hasn't run yet — workflows from first-time fork contributors need the "Approve and run" click, so the combined status is still Whenever someone with write access has a moment, this needs an approve-and-run and then an enqueue from your side. Happy to rebase or adjust anything in the meantime. For convenience, the test run locally on this branch: |
Follow-up to the same file's corrected values list. The multi-hook precedence line still presented 'defer' as a decision value, contradicting the note above it. Precedence is deny > ask > allow; a hook with no opinion omits permissionDecision entirely.
The bug
question-preference-hookreturns{"permissionDecision":"defer"}on its no-opinion path, intending "let the permission flow continue."deferis not in Claude Code's PreToolUse schema, which accepts onlyallow/deny/ask. As of Claude Code 2.1.14 the unrecognized value is interpreted as defer the tool call: the call is dropped and logged as ahook_deferred_toolattachment. It never executes.For
AskUserQuestionthis means the question widget never renders. The user sees nothing at all, and the agent is left waiting on an answer that can never arrive.Captured in a real transcript:
Blast radius
This is not limited to users with plan-tune preferences configured.
defer()is the default path for every non-auto-decidable question:always-askpreferenceSo
AskUserQuestionis broken for essentially every user on Claude Code 2.1.14 with gstack installed.The fix
Omit
permissionDecisionentirely indefer(). That is the correct "no opinion" signal — normal permission flow continues, andadditionalContext(the memory-cache injection) is still delivered.The
deny()paths are untouched: never-ask auto-decide and the Conductor prose redirect still enforce exactly as before.Docs
docs/spikes/claude-code-hook-mutation.mddocumented"defer" — let permission flow continueas a valid value. That stale assumption is how this got written, so it's corrected here to prevent reintroduction.Tests
Defer-path assertions changed from
toBe('defer')totoBeUndefined(), matching the corrected contract.Verified end-to-end on Claude Code 2.1.14: with the patch applied,
AskUserQuestionrenders and returns answers normally. Both PostToolUse AUQ hooks (question-log-hook,auq-error-fallback-hook) were also checked and are unaffected.