AskUserQuestion always fails on Claude desktop: question-preference-hook returns invalid permissionDecision 'defer'
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
`question-preference-hook.ts` returns `permissionDecision: 'defer'` from its `defer()` path. **`defer` is a print-mode-only value.** In the Claude **desktop** app this fails validation and kills the `AskUserQuestion` call outright — the question never renders and the caller gets:
```
[Tool result missing due to internal error]
```
Because `defer()` is the fall-through for every question that isn't auto-decided, **effectively every AskUserQuestion call is broken on Claude desktop** when gstack's hooks are installed.
Reproduced on gstack **v1.60.1.0** (`a325940`, current `origin/main`, 0 commits behind), Claude Desktop 1.15962.1, Claude Code 2.1.187/2.1.219, macOS 26.5.1.
## Why it looks like a Claude-side bug (it isn't)
The CLI tolerates the invalid value; the desktop app does not. From the Claude Code binary:
```
permissionDecision` - "allow", "deny", or "ask" (PreToolUse only)
permissionDecision=defer in interactive mode; ignoring (defer is print-mode only)
```
So the CLI logs a warning and proceeds — which is why interactive prompts work in the terminal but fail in the desktop app with the same hook. The desktop bundle only ever emits/accepts `allow` / `deny` / `ask`.
This cost me a multi-hour investigation that eliminated: app restart (soft + clean with helpers killed), a crashing MCP server (256 setup crashes → 0, no change), all other MCP servers, tool-count/process/memory overload (250 → ~200 tools, tree cleared), and VPN/DNS asset-fetch failures (`assets-proxy.anthropic.com` 3.08s → 0.02s, 463 `Failed to fetch` → HTTP 200). None of it mattered — it was the hook the whole time.
## Repro
```bash
echo '{"session_id":"t","cwd":"'"$PWD"'","hook_event_name":"PreToolUse","tool_name":"AskUserQuestion","tool_input":{"questions":[{"question":"Test?","header":"T","multiSelect":false,"options":[{"label":"A","description":"a"},{"label":"B","description":"b"}]}]}}' \
| ~/.claude/skills/gstack/hosts/claude/hooks/question-preference-hook
```
Current output:
```json
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"defer"}}
```
Then call `AskUserQuestion` from the Claude desktop app → no card renders, tool errors.
## Fix
In `hosts/claude/hooks/question-preference-hook.ts`, `defer()` should omit `permissionDecision` entirely — omission is the portable "no opinion" signal and works on both surfaces. `additionalContext` still rides along fine.
```diff
const out: Record = {
hookEventName: 'PreToolUse',
- permissionDecision: 'defer',
};
```
Verified locally: with the field omitted, `AskUserQuestion` renders and returns normally in the Claude desktop app, and preference enforcement (`deny` + auto-decide) is unaffected.
## Possibly related
Several commits appear to work around AUQ "being unreliable" rather than addressing this:
- `v1.57.2.0` — "AskUserQuestion prose fallback when the tool fails at runtime" (#1908)
- `v1.58.1.0` — "Conductor prose AskUserQuestion" (#2004) — the `isConductor()` branch denies AUQ because "native is disabled, the MCP variant is flaky"
- `v1.56.0.0` — "AUQ paranoid safety net" (#1849)
It may be worth re-testing whether the underlying "AUQ is flaky" premise still holds once the invalid `defer` is removed.
Contributor guide
Research direction
Start in hosts/claude/hooks/question-preference-hook.ts and inspect the defer() path. Run the provided AskUserQuestion hook reproduction to confirm the current output, then verify that the field is omitted while additionalContext and preference enforcement remain intact. Done means AskUserQuestion renders normally in Claude Desktop without the invalid permissionDecision value.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100