Skip to content

fix(plan-tune): stop deferring AskUserQuestion in non-interactive sessions#2349

Closed
mitch-schneider-1 wants to merge 1 commit into
garrytan:mainfrom
mitch-schneider-1:fix/auq-defer-print-mode
Closed

fix(plan-tune): stop deferring AskUserQuestion in non-interactive sessions#2349
mitch-schneider-1 wants to merge 1 commit into
garrytan:mainfrom
mitch-schneider-1:fix/auq-defer-print-mode

Conversation

@mitch-schneider-1

Copy link
Copy Markdown

Summary

The AskUserQuestion PreToolUse hook emits permissionDecision: "defer" on its pass-through path — no preference set, no <gstack-qid:…> marker, i.e. the common case. This makes AskUserQuestion silently fail in every non-interactive host while working perfectly in the terminal.

Root cause

defer is an undocumented, print-mode-only value. The documented set is allow | deny | ask. From the Claude Code 2.1.216 binary:

- `permissionDecision` - "allow", "deny", or "ask" (PreToolUse only)
Hook … returned permissionDecision=defer in interactive mode; ignoring (defer is print-mode only)
Unknown hook permissionDecision type: … Valid types are: allow, deny, ask, defer

So:

Host Behavior
Interactive terminal Value parsed, warning logged, ignored → tool works
Non-interactive (Cowork, Agent SDK, claude -p) Value honored → tool_use left unresolved, no result

In the non-interactive case the agent receives [Tool result missing due to internal error] and the question never reaches the user.

The failure mode is nasty precisely because it's mode-split: 100% broken in one host, 100% fine in another. That reads as flaky transport, so it gets misattributed to the platform rather than the hook. It also defeats the existing auq-error-fallback-hook mitigation, since a deferred tool never produces the error response that hook keys on.

Fix

Emit no permissionDecision on pass-through. Absence is the correct "no opinion" signal and the only form that is safe in both modes.

Unchanged:

  • additionalContext (plan-tune memory injection) still emitted when present.
  • The deny paths — never-ask auto-decide and the Conductor prose redirect — are untouched.

Also corrects docs/spikes/claude-code-hook-mutation.md, which documented defer as the pass-through shape. That's where the bug originated, so leaving it would re-seed the regression.

Testing

  • 89 pass / 0 fail across the 7 AskUserQuestion hook test files.
  • Updated 11 assertions across 3 test files that pinned the old defer contract; they now assert absence.
  • Verified live: AskUserQuestion confirmed working in a Cowork session where it had consistently returned no result.
  • The 5 skill-e2e-plan-tune-cathedral eval scenarios fail identically on main and on this branch (pre-existing, unrelated — per the repo's E2E blame protocol).

Health check — empty stdout is correct:

echo '{"hook_event_name":"PreToolUse","tool_name":"AskUserQuestion","tool_input":{"questions":[]}}' \
  | hosts/claude/hooks/question-preference-hook

🤖 Generated with Claude Code

…sions

The AskUserQuestion PreToolUse hook emitted `permissionDecision: "defer"`
on its pass-through path (no preference, no marker — the common case).

`defer` is an undocumented print-mode-only value; the documented set is
`allow | deny | ask`. Interactive Claude Code parses it, logs "defer is
print-mode only", and ignores it — so the hook looks correct in a
terminal. Non-interactive sessions (Cowork, the Agent SDK, `claude -p`)
honor it and leave the tool_use unresolved with no result, which the
agent receives as "[Tool result missing due to internal error]".

Net effect: AskUserQuestion was 100% dead in non-interactive hosts and
100% fine in the terminal, which reads as flaky transport rather than a
hook bug and is easy to misattribute to the platform.

Fix: emit no `permissionDecision` on pass-through. Absence is the
correct "no opinion" signal and is the only form safe in both modes.
`additionalContext` (plan-tune memory injection) is preserved, and the
`deny` paths — never-ask auto-decide and the Conductor prose redirect —
are unchanged.

Also corrects docs/spikes/claude-code-hook-mutation.md, which documented
`defer` as the pass-through shape and is where the bug originated.

Verified: 89 pass / 0 fail across the 7 AskUserQuestion hook test files,
and AskUserQuestion confirmed working live in a Cowork session where it
had previously returned no result.

Co-Authored-By: Claude <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jul 26, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@mitch-schneider-1

Copy link
Copy Markdown
Author

Closing as a duplicate — #2325 already covers this fix and touches the same files. I missed it before opening; apologies for the noise.

Leaving one note here in case it's useful to whoever picks #2325 up: the reason this reads as flaky transport rather than a hook bug is that defer is honored in print mode but ignored in interactive mode, so the same hook is 100% broken in Cowork / Agent SDK / claude -p and 100% fine in a terminal. From the Claude Code 2.1.216 binary:

- `permissionDecision` - "allow", "deny", or "ask" (PreToolUse only)
Hook … returned permissionDecision=defer in interactive mode; ignoring (defer is print-mode only)

Worth also fixing docs/spikes/claude-code-hook-mutation.md, which documents defer as the pass-through shape — that's where the bug originated, so it can re-seed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant