Bug
hosts/claude/hooks/question-preference-hook.ts's pass-through path (the common case: a plain AskUserQuestion call with no <gstack-qid:...> marker, or one that doesn't match a never-ask preference) resolves via:
function defer(additionalContext?: string): void {
const out = { hookEventName: 'PreToolUse', permissionDecision: 'defer', ... };
process.stdout.write(JSON.stringify({ hookSpecificOutput: out }));
process.exit(0);
}
This hits a confirmed Claude Code SDK race: anthropics/claude-code#64389 — a PreToolUse hook resolving permissionDecision: "defer" can lose a race in the SDK and get fed a fabricated is_error: true, "[Tool result missing due to internal error]" tool_result instead of suspending correctly. A commenter on that thread confirms it reproduces against AskUserQuestion specifically, not just Bash.
Since this hook matches every AskUserQuestion / mcp__*__AskUserQuestion call, and the pass-through defer() branch is what fires for any plain question without gstack's internal marker (i.e. most real usage, any time a skill or the base agent asks a free-form question), this made AskUserQuestion unreliable-to-broken in the Claude Code VS Code extension host in my testing — deterministically failing across repeated calls in one session.
Root cause is upstream, but the workaround is local
The claude-code#64389 thread has two purported fixes:
- Delay the
defer() write until after the assistant message stream closes (~200ms, reported as "deterministic across dozens of runs" by one reporter).
- Use
allow/deny/ask instead of defer — the thread states plainly these are not affected by the race.
I tried (1) first; it did not reproduce reliably in my environment — still failed after adding the delay. Switched to (2): since AskUserQuestion has no real "normal permission system" step for defer to fall through to (unlike a gated Bash command), swapping the pass-through case to permissionDecision: "allow" is behaviorally equivalent here, and it resolved the issue — confirmed working across repeated live calls afterward.
Suggested fix
In defer() in question-preference-hook.ts (and check auq-error-fallback-hook.ts / any other PreToolUse hook using defer for AskUserQuestion for the same pattern), change:
permissionDecision: 'defer',
to
permissionDecision: 'allow',
for the pass-through case specifically (the enforcement deny() path is unaffected and doesn't need to change). Happy to open a PR with this if useful — for now filing as an issue since I wasn't sure whether you'd want this landed as-is or handled differently upstream (e.g. if defer is relied on elsewhere for permission-mode interop I haven't considered).
Environment
CLAUDE_CODE_ENTRYPOINT=claude-vscode, Claude Code VS Code extension, agent SDK 0.3.207, macOS.
Bug
hosts/claude/hooks/question-preference-hook.ts's pass-through path (the common case: a plainAskUserQuestioncall with no<gstack-qid:...>marker, or one that doesn't match a never-ask preference) resolves via:This hits a confirmed Claude Code SDK race: anthropics/claude-code#64389 — a
PreToolUsehook resolvingpermissionDecision: "defer"can lose a race in the SDK and get fed a fabricatedis_error: true,"[Tool result missing due to internal error]"tool_result instead of suspending correctly. A commenter on that thread confirms it reproduces againstAskUserQuestionspecifically, not just Bash.Since this hook matches every
AskUserQuestion/mcp__*__AskUserQuestioncall, and the pass-throughdefer()branch is what fires for any plain question without gstack's internal marker (i.e. most real usage, any time a skill or the base agent asks a free-form question), this madeAskUserQuestionunreliable-to-broken in the Claude Code VS Code extension host in my testing — deterministically failing across repeated calls in one session.Root cause is upstream, but the workaround is local
The claude-code#64389 thread has two purported fixes:
defer()write until after the assistant message stream closes (~200ms, reported as "deterministic across dozens of runs" by one reporter).allow/deny/askinstead ofdefer— the thread states plainly these are not affected by the race.I tried (1) first; it did not reproduce reliably in my environment — still failed after adding the delay. Switched to (2): since
AskUserQuestionhas no real "normal permission system" step fordeferto fall through to (unlike a gatedBashcommand), swapping the pass-through case topermissionDecision: "allow"is behaviorally equivalent here, and it resolved the issue — confirmed working across repeated live calls afterward.Suggested fix
In
defer()inquestion-preference-hook.ts(and checkauq-error-fallback-hook.ts/ any other PreToolUse hook usingdeferfor AskUserQuestion for the same pattern), change:to
for the pass-through case specifically (the enforcement
deny()path is unaffected and doesn't need to change). Happy to open a PR with this if useful — for now filing as an issue since I wasn't sure whether you'd want this landed as-is or handled differently upstream (e.g. ifdeferis relied on elsewhere for permission-mode interop I haven't considered).Environment
CLAUDE_CODE_ENTRYPOINT=claude-vscode, Claude Code VS Code extension, agent SDK 0.3.207, macOS.