AskUserQuestion PreToolUse hook's defer() hits claude-code#64389 race — fix: use allow instead
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Bug
`hosts/claude/hooks/question-preference-hook.ts`'s pass-through path (the common case: a plain `AskUserQuestion` call with no `` marker, or one that doesn't match a never-ask preference) resolves via:
```js
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](https://github.com/anthropics/claude-code/issues/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:
1. Delay the `defer()` write until after the assistant message stream closes (~200ms, reported as "deterministic across dozens of runs" by one reporter).
2. 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:
```js
permissionDecision: 'defer',
```
to
```js
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.
Contributor guide
Research direction
Start in hosts/claude/hooks/question-preference-hook.ts and inspect defer(), then check hosts/claude/hooks/auq-error-fallback-hook.ts and other AskUserQuestion PreToolUse hooks for the same pattern. Verify the pass-through path uses allow while the enforcement deny path remains unchanged, and confirm repeated AskUserQuestion calls no longer produce the reported missing tool result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 78/100