Summary
_fix_phase ends its loop with a bare return Decision(Action.DEFER) (engine.py:4675) —
no reason string at all. Every other decision site in the engine names why it gave up, and the
defer reason is what an operator reads and what _record_defer journals as story-deferred
reason=.
if terminal is not None:
return terminal
if ok:
return Decision(Action.PROCEED)
return Decision(Action.DEFER) # <- engine.py:4675, reason=""
Contrast the peers, all of which carry one:
| site |
reason |
decide_dev non-completed |
session_failure_reason("dev", result) |
decide_review_session non-completed |
session_failure_reason("review", result) |
| blocking plugin workflow |
session_failure_reason("blocking workflow 'x' (y)", result) |
| sweep migration / triage |
session_failure_reason("migration"/"triage", result) |
| fix session, env-fault |
env_fault_pause_reason("fix", result) — this leg is fine |
| fix session, everything else |
nothing |
env_fault_pause_reason's own docstring enumerates "fix" as a member of that peer set, so
the omission reads as an oversight rather than a decision.
Why it matters
A repair session is dispatched precisely because the story already failed once. When the repair
also fails for a non-env-fault reason — timeout, stall, over-budget, a crash, or the mux losing
the session under it — the story plateau-defers with an empty explanation, and the journal entry
carries only session_status. The operator sees a story that stopped and no statement of what
stopped it, on the one path where they already know something was wrong.
Suggested shape
Give the terminal DEFER the reason it already has in hand — the fix session's own
session_failure_reason("fix", result), or the last verify outcome's reason when the session
completed but verification did not pass. No routing change; this is the same
diagnosis-not-routing shape as #489.
Provenance
Found by the adversarial review of the #489 Q1 change (two independent review layers flagged it
against different evidence). Pre-existing, unrelated to that change's crash path, and deliberately
left out of it rather than widening an in-flight diff.
Refs #489
Summary
_fix_phaseends its loop with a barereturn Decision(Action.DEFER)(engine.py:4675) —no reason string at all. Every other decision site in the engine names why it gave up, and the
defer reason is what an operator reads and what
_record_deferjournals asstory-deferredreason=.Contrast the peers, all of which carry one:
decide_devnon-completedsession_failure_reason("dev", result)decide_review_sessionnon-completedsession_failure_reason("review", result)session_failure_reason("blocking workflow 'x' (y)", result)session_failure_reason("migration"/"triage", result)env_fault_pause_reason("fix", result)— this leg is fineenv_fault_pause_reason's own docstring enumerates"fix"as a member of that peer set, sothe omission reads as an oversight rather than a decision.
Why it matters
A repair session is dispatched precisely because the story already failed once. When the repair
also fails for a non-env-fault reason — timeout, stall, over-budget, a crash, or the mux losing
the session under it — the story plateau-defers with an empty explanation, and the journal entry
carries only
session_status. The operator sees a story that stopped and no statement of whatstopped it, on the one path where they already know something was wrong.
Suggested shape
Give the terminal
DEFERthe reason it already has in hand — the fix session's ownsession_failure_reason("fix", result), or the last verify outcome's reason when the sessioncompleted but verification did not pass. No routing change; this is the same
diagnosis-not-routing shape as #489.
Provenance
Found by the adversarial review of the #489 Q1 change (two independent review layers flagged it
against different evidence). Pre-existing, unrelated to that change's crash path, and deliberately
left out of it rather than widening an in-flight diff.
Refs #489