Fix: decision cards never appear in the Claude desktop app (AskUserQuestion parked by permissionDecision 'defer') - #2474
Open
habassa5 wants to merge 1 commit into
Conversation
The PreToolUse hook for AskUserQuestion emitted `permissionDecision: 'defer'` on its no-enforcement path. In Claude Code, 'defer' does not mean "no opinion" — it means "park this tool call so a later resume can pick it up". It is ignored in interactive sessions but HONORED whenever the session is flagged non-interactive, which includes the Claude desktop app even with a human present. The result: every decision card was parked, never ran, and never returned. The user saw nothing and the model received "[Tool result missing due to internal error]". This is why it went unnoticed — in the terminal CLI 'defer' is ignored and cards work fine. The contract of this path is "no enforcement — proceed normally, let the user be asked" (see the never-ask + one-way safety override, which requires the question to still reach the human; parking it would invert that guarantee). Claude Code encodes that by omitting permissionDecision entirely — it is .optional() in the PreToolUse hookSpecificOutput schema. additionalContext is still emitted, so plan-tune memory injection is unaffected, and the deny paths (never-ask auto-decide, Conductor prose redirect) are untouched. Updates 14 assertions that encoded the old contract and adds a regression guard asserting the hook never emits 'defer'. Fixes garrytan#2466 Co-Authored-By: Claude Opus 5 <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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2466 — AskUserQuestion decision cards never appear when gstack runs in the Claude desktop app. The question is silently swallowed, the user sees nothing, and the agent gets back
[Tool result missing due to internal error].hosts/claude/hooks/question-preference-hook.tsemittedpermissionDecision: 'defer'on its no-enforcement path. In Claude Code,deferdoes not mean "no opinion" — it means park this tool call so a later resume can pick it up.From the Claude Code 2.1.221 binary:
deferis honored only when the session is flaggedisNonInteractiveSession— and the Claude desktop app is flagged non-interactive even with a human present. So the AskUserQuestion call is parked, never executes, and never returns.This is why it went unreported: in the terminal CLI
deferis ignored and cards work fine, which is how most people run gstack. It reproduces only in the desktop app.The fix
Omit
permissionDecisionon the no-enforcement path. It is.optional()in the PreToolUsehookSpecificOutputschema:Omitting it is also what this path already means. The hook's own docstring states:
If
deferparked the tool, a one-way door would never reach the human — the exact inverse of the documented safety override. So the intent is "no enforcement, proceed normally, let the user be asked."Only
allow/deny/askare safe to emit when the tool is expected to actually run.What is preserved
additionalContextstill flows → plan-tune memory-nugget injection unaffecteddenypaths untouched → never-ask auto-decide and the Conductor prose redirect both still work (denyis a valid, honored value)Verification
Bisected on a real desktop-app session with all three AUQ hooks registered:
question-preference-hookonly (emitsdefer)[Tool result missing due to internal error]❌Across that machine's full transcript history before the fix: 3,700+ tool calls of every other kind resolved 100%; AskUserQuestion went 0-for-8. After the fix, decision cards render and return normally, including multi-question and
multiSelectcards.Tests
14 assertions across three suites encoded the old contract and are updated to
toBeUndefined(). Added a regression guard (never emits permissionDecision:defer) covering the no-preference, no-marker, empty-stdin, and non-AUQ paths, plus the one-way-door safety override.93/93 passing across the five hook suites against current
main(v1.60.2.0):🤖 Generated with Claude Code