Skip to content

Failure handling

The factory has no cycle limit, no turn limit and no timeout for the plan, implement and diagnose agents. A failed try is information, so each one goes through the same path.

flowchart TD
    F[A layer fails] --> D[Diagnosis agent reads the failure and the code]
    D -->|change_defect| R[Implement again with cause and direction]
    D -->|test_or_environment| R2[Implement again, told not to change code only to pass]
    D -->|wrong_approach| P[Plan again, then implement]
    D -->|criteria_conflict| S[Stop and ask the owner]
    R --> K{Same failure seen before?}
    R2 --> K
    P --> K
    K -->|no| N[Next cycle]
    K -->|yes| RP{Planned again twice already?}
    RP -->|no| P2[Plan again with the repeated failure]
    RP -->|yes| S
    P2 --> N
Layer Where it is caught
The unit tests and integration tests Test-Change, from the gate’s failure summary.
Acceptance in real Flare Invoke-Acceptance, from the checks the agent marked as failed.
The required checks on the pull request The failing run’s log tail, from gh run view --log-failed.
A conflict with main Sync-WithMain.
An implement session that produced no change The cycle loop.

A push that changes nothing compared with the last push is also a failure. The next try is told that the last try changed nothing and is shown the failure it was meant to fix.

Get-Diagnosis starts a read-only agent that can read the plugin source and cannot run or change anything. It gets the issue, the plan, the failed layer, up to 120 lines of the failure and up to 400 lines of the diff. It returns structured output defined by diagnose.schema.json with a kind, a cause, evidence and a direction.

Kind Meaning What the factory does
change_defect The change has a mistake and the approach is sound. Hands cause, evidence and direction to the next implement pass.
wrong_approach The approach cannot work. Plans again with the diagnosis, then implements the new plan.
test_or_environment The failure is not about the change, for example a flaky test or a machine problem. Implements again, with a note not to change code only to make it pass.
criteria_conflict The requirements cannot all be met, or a test and the issue disagree. Stops and asks you to decide.

If the diagnosis itself fails, the raw failure goes to the implementer unchanged. An infrastructure error inside the diagnosis still stops the run.

Each failure is reduced to a key by removing text in parentheses, replacing numbers with # and collapsing whitespace. The run counts how often each key appears. The second time the same key appears, the run records a stall event, adds a catch and plans again with the repeated failure. After a replan the counts start over.

If the run has already planned again twice because of stalls and the same failure comes back, it stops and hands the issue to you with the last failure. That is the only place a run gives up on a failing test, and it is a decision about a repeated failure, not a count of cycles.

An implementer that says CANNOT_BUILD also sends the run back to planning, with its own words as the reason.

Stop-Infra is the only way a run ends early. It prints STOPPED and the reason, writes a run.stop event, removes the run’s claim, comments on the issue and exits with code 1. A stopped run can be started again once the cause is fixed.

Cause Why it stops
The Claude login has expired Only you can sign in again.
The Claude usage limit was reached Try again later.
The claude command cannot start Something on the machine is broken.
The code graph cannot be updated The gate cannot know which tests to run.
The acceptance check cannot run twice in a row The Flare VM or the agent is unusable. The change was not checked in Flare.
A gh call or a git push fails The factory cannot reach GitHub.
Another live process already builds this issue Two runs would collide.
The diagnosis says criteria_conflict A decision is needed.
The same failure survived two replans A decision is needed.
The planner says it cannot plan the issue, or any other unexpected error Reported as an error that is not a build failure.

Failing tests, failing checks and failing acceptance never stop a run. Neither do turn counts or time.

The acceptance agent is the one exception to “no caps”. It has a 25 minute timeout and a limit of 60 turns, because it drives a VM and a stuck session would hold the line for everyone.

An acceptance check has three results. A pass needs evidence, a value read or something seen in a screenshot. A fail sends the run back to implement. A could_not_verify is used only after the agent tried and the tools could not do it, for example a real mouse hover. It does not fail the change, and the list is written on the issue when the pull request merges, and in the catch log.

Add-Catch writes one line per failure to a JSON Lines catch log on the owner’s machine, with the layer that found it. The layers are unit-tests, acceptance, unverified, ci, diagnosis and stall. scripts/catches.ps1 prints counts per layer and the latest 15 catches, or one issue with -Issue. It shows which layer catches what.