fix: stop recovery after question cards - #9320
Conversation
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of The design story checks out end-to-end: the flag is recorded at both directive-consumption paths (marker and out-of-band), only on the delivery-success outcome, and gates only the empty-response elif — delivery failures and generic tool-only turns still fall through to the existing ladder. The string-prefix coupling is mitigated by the shared constant, and the new integration test pins the seam so wording drift breaks red, not silent. The owning spec ( Design-Verdict: PASS Terminal-outcome flag is recorded at the delivery point of truth, gates only the empty-response arm, and failure paths keep the plain-text fallback. [DESIGN-REVIEWED] 0340669 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsThe single candidate concerns the post-compaction continuation arm at The candidate requires me to establish that this conjunction occurs in practice. I cannot: mid-turn compaction is a context-ceiling event whose co-occurrence with a successful non-blocking question card in a single turn is not something I can derive at the required confidence from the code — the candidate's (a) resolves to "rare but reachable," which is the "could/might" case Step 1 drops. It is also on an unchanged line whose behavior the PR neither introduces nor worsens (before this PR the empty-response ladder fired on the same shape). It fails the falsification bar and does not survive. The changed code itself is sound: both record sites ( No findings. [OPUS-REVIEWED] 0340669 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of I have everything I need. Checking the one remaining count — usages of the new prefix constant and the directive kinds sharing the "end your turn" outcome shape are confirmed (grep "End your turn" in First-Principles-Verdict: CONCERNS The fix is real and mechanism-level, but it terminalizes only What this change shipsIntent: stop an answered/dismissed question card from reappearing because the runner "recovers" a turn the tool deliberately ended silently — a FIX.
Watch
[FIRST-PRINCIPLES-REVIEWED] 0340669 |
61cfa96 to
3853453
Compare
The successful-card prefix is now defined beside |
The sibling observation is legitimate: successful monitor/subagent paths also instruct the model to end, and a structured terminal-tool contract would be broader than this question-card incident fix. This PR now narrows its Pattern harvest to the non-blocking question-card defect rather than claiming that whole class is closed. I attempted the repository's required |
|
CI note: the Evidence:
I am leaving this unrelated base repair out of #9320. After #9322 lands, this branch should rebase onto the fixed |
3853453 to
0340669
Compare
|
Update: the Post-rebase targeted validation (single process, no full local suite):
The inherited Windows/coverage reds from the prior head should clear in this new CI round. |
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: fix (4 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: a successfully delivered non-blocking ask_question card is the turn's intended terminal output, but empty-response recovery treated it as a generic tool-only turn and injected a closing continuation that could re-post the same card; the runner now records the delivered-card outcome and skips the empty-response ladder, while delivery failures keep the old fallback. Spec files changed as a ride-along (a minority of the diff on both file count and changed lines), not reviewed as a design decision: docs/system-specs/modules/session.md. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.
Problem / Motivation
A successful non-blocking
ask_questioncall posts a dashboard card and explicitly tells the model to end its turn without assistant text. The dashboard runner currently treats that intentional tool-only ending as an empty response and queues a synthetic continuation. The continuation can re-enter the same unresolved question intent and post the same card again.Why it matters
A user can become trapped behind a question card that reappears after they answer or dismiss it. Repeated recovery turns also waste model work and make the session appear to ignore the user's response.
What changed (motivation → approach → change)
The card itself is the intended terminal output, so the fix records a turn-local terminal outcome only when
ask_questionwas successfully delivered to an attached dashboard client. The marker and out-of-band directive paths both feed that outcome. The successful-card prefix is defined beside the directive applier and imported by the runner, so control flow cannot silently drift from outcome wording. The empty-response ladder is skipped for that one successful terminal case.Delivery failures keep the existing behavior, allowing the model to fall back to asking in plain text. Other tool-only turns still receive the normal closing-continuation recovery.
Tests
Manual verification
N/A — the integration test reproduces the exact tool-call/result/turn-complete shape and observes both card delivery and the runner queue.
Related Issues
No linked issue: field-reported incident without a public issue.
Pattern harvest
Rule candidate: review-prompt
Pattern: A successfully delivered non-blocking question card must terminate without generic missing-closing-reply recovery.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)