Skip to content

engine: a non-completed fix session plateau-defers with no reason at all #521

Description

@dracic

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions