garrytan / garrytan/gstack

question-preference-hook emits invalid permissionDecision "defer" → aborts every AskUserQuestion call

Open Beginner friendly
#2,310 2 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: `question-preference-hook` emits `permissionDecision: "defer"` → aborts every AskUserQuestion call

**Repo:** garrytan/gstack · **Version:** v1.60.1.0 · **Host:** Claude Code (current)

## Summary

The PreToolUse hook `hosts/claude/hooks/question-preference-hook.ts` returns
`permissionDecision: "defer"` on its no-op path (the `defer()` helper). The
current Claude Code harness does not honor `"defer"` as a permission decision —
it treats the hook output as invalid and **aborts the tool call**. The user sees
`Tool execution was interrupted` / `[Tool result missing due to internal error]`.

Because `defer()` is the overwhelmingly common return path (every question that
isn't a marker-tagged, never-ask, two-way door with an unambiguous recommended
option), **AskUserQuestion fails on essentially every call.** The `deny()` path
is unaffected because `"deny"` is a valid value.

## Reproduction

1. On Claude Code with gstack hooks enabled, trigger any `AskUserQuestion` call
without a matching never-ask preference.
2. Hook returns `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"defer"}}`.
3. Tool aborts every time.

Direct hook check:
```
echo '{"tool_name":"AskUserQuestion","tool_input":{"questions":[{"question":"Q?","options":[{"label":"A"},{"label":"B"}]}]}}' \
| hosts/claude/hooks/question-preference-hook
# → {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"defer"}}
```

## Root cause

Valid `permissionDecision` values are `"allow" | "deny" | "ask"`. `"defer"` is
not honored. The canonical "no opinion, proceed with the normal permission flow"
signal is to **omit `permissionDecision` entirely** (exit 0, empty stdout, or
`hookSpecificOutput` carrying only `additionalContext`).

## Fix

In the `defer()` helper, stop emitting `permissionDecision: "defer"`. Emit empty
stdout + exit 0 when there's no `additionalContext`; otherwise emit
`hookSpecificOutput` with `hookEventName` + `additionalContext` only:

```ts
function defer(additionalContext?: string): void {
if (!additionalContext) {
process.exit(0);
}
process.stdout.write(
JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
additionalContext,
},
}),
);
process.exit(0);
}
```

This preserves the plan-tune Layer-8 memory-context feature (via
`additionalContext`) and the enforcement `deny()` path, while unbreaking every
non-enforced question. Verified: patched hook returns empty/exit 0 and
AskUserQuestion works end-to-end.

Contributor guide

Open the contributing guide

Research direction

Start with hosts/claude/hooks/question-preference-hook.ts and inspect the defer() helper and its callers. Run the direct hook check from the issue, then verify that the no-op path produces no invalid permissionDecision while additionalContext is preserved and the AskUserQuestion call completes normally.

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
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.