ask/resume: nonzero exit on refusal and pause_turn stop reasons - #578
Conversation
Follow-up at the #576/#577 seam: the final_answer protocol (mu-bm6za) makes completion a protocol event so headless callers stop scraping stdout — but a safety-refused ask (stop_reason=refusal, from #576) can never call final_answer, and previously fell through the stop_reason check in BOTH `mu ask` and `mu resume --prompt` to exit 0: a non-answer scoring as clean success. Same class as the mu-1mvq max_tokens lesson, same fix: bail with a legible stderr line. pause_turn likewise exits nonzero ("output may be partial"), mirroring the degraded_eof arm, in both paths. Also: map_stop_reason test now asserts the gen-5 mappings positively and its comment no longer claims refusal/pause_turn fold to EndTurn (stale since #576).
|
Board outcome (consensus panel, live-seat adjudication): kimi-k3 APPROVE after its round-3 findings landed (resume.rs shared-path gap — fixed; stale map_stop_reason test comment — fixed with positive asserts; its claim that the test still ASSERTED the old mappings was checked against the file and refuted — the suite was green throughout). glm-5.2's final position was an unverified hypothetical (refusal coexisting with a completed final_answer call), adjudicated in-code: the documented refusal shape is an empty turn, and if the combination ever occurs, nonzero-exit-with-answer-printed is the conservative behavior — comment added at the ask.rs arm. Dead seats unchanged all night (claude seats skipped under cc-nesting; gpt-5.5 codex seat broken-pipe — tracked on the bead). Also filed during gating: mu-dialogue-presence-test-flake-eypd9 — |
Post-merge check of #576 against #577 (final_answer, mu-bm6za), per operator request. The two compose cleanly at the loop level — a
final_answercompletion lands asstop_reason=tool_use→TaskExitReason::Done, and a refusal (which never calls the tool) now ends the ask promptly under my #576 guard instead of grinding for correctness. One seam gap:mu askandmu resume --promptbail onmax_tokens/degraded_eofso fragments can't exit 0 (the mu-1mvq lesson), but the newrefusal/pause_turnfell through to exit 0 — a refused headless ask would score as a clean success with no answer, exactly what the final_answer protocol exists to prevent.Fix: both paths bail with legible stderr lines (
refusal= no answer, consider another seat;pause_turn= output may be partial, mirroring degraded_eof). Also updates themap_stop_reasontest to assert the gen-5 mappings positively — its comment still claimed refusal/pause_turn fold to EndTurn (stale since #576).Edge (panel-raised, adjudicated in-code): if a refusal ever cut a turn that also completed a
final_answercall, the answer still prints but the exit is nonzero — deliberate; a classifier-cut answer should force caller scrutiny.Bead: mu-provider-drift-2026q3-y43la.