garrytan / garrytan/gstack

AskUserQuestion PreToolUse hook's defer() hits claude-code#64389 race — fix: use allow instead

Open Beginner friendly
#2,292 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.