fix(missions): a retry's work is no longer silently destroyed
Two independent bugs, either of which loses everything a retried phase
produced, and neither of which reports a failure.
1. Capture is suppressed forever on a retry. Both
`capture_finished_coding_phases` and the sweeper's last-chance
`capture_outstanding_phases` skip any phase that already has a
`code_diff` artifact. That guard is right for a phase that ran once and
catastrophic for a retried one: the artifact from the FAILED attempt
suppresses capture of the new attempt, the container is reaped on its
normal grace, and everything the agents committed inside it is gone.
The UI keeps showing the old diff, so the mission reads as delivered.
`retry_phase` now clears the reopened phases' captures in the same
transaction that reopens them, which is what makes its own doc comment
("the phase card starts fresh on the retry") true of the artifacts too.
2. `git add` exits non-zero over a gitignored path while staging correctly.
Measured: with a populated `target/`, `git add -- . :(exclude)target`
exits 1 and stages the right files; `-c advice.addIgnoredFile=false`,
`--ignore-errors`, `-A` and `:/` all behave identically. Propagating
that with `?` aborted the commit AFTER a successful staging — no branch,
no commit, no push — for every Rust repo an agent has built in.
`capture_phase_diff_at` already treats the same command as advisory;
the commit path now does too, and the staged index decides.
Mission 01a00538 hit both: it completed research and coding on the retry,
11 agent commits and all, delivered a patch dated the previous day, and
lost the commits when the container was reaped. The remote was never
touched — its HEAD still equalled the mission's own base_sha.
Covered by a test that drives real git and asserts the files are staged
regardless of the exit code.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fd5e71ccfe
commit
10341cf7fe
@@ -1135,6 +1135,33 @@ pub async fn retry_phase(
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
// Drop the previous attempt's capture, or this retry's work is DESTROYED.
|
||||
//
|
||||
// `capture_finished_coding_phases` skips any phase that already has a
|
||||
// `code_diff` artifact (`NOT EXISTS`, phase_runner.rs). That guard is right
|
||||
// for a phase that ran once, and catastrophic for a retried one: the stale
|
||||
// artifact from the failed attempt suppresses capture of the new attempt
|
||||
// forever, the container is then reaped on its normal grace, and everything
|
||||
// the agents committed inside it is gone. The UI meanwhile shows the OLD
|
||||
// diff, so the mission reads as delivered.
|
||||
//
|
||||
// That is exactly what happened to mission 01a00538: it completed both
|
||||
// phases on the retry, 11 agent commits and all, and delivered a patch
|
||||
// dated the previous day. Deleting here is what makes the doc comment above
|
||||
// ("the phase card starts fresh on the retry") true of the artifacts too.
|
||||
let cleared = sqlx::query(
|
||||
"DELETE FROM mission_artifacts a
|
||||
USING mission_phases mp
|
||||
WHERE a.phase_id = mp.id
|
||||
AND a.mission_id = $1
|
||||
AND mp.status = 'pending'
|
||||
AND a.kind = 'code_diff'",
|
||||
)
|
||||
.bind(id)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
// And put the mission back to running, or nothing sweeps the phase: every
|
||||
// launcher and closer keys off `missions.status = 'running'`.
|
||||
sqlx::query(
|
||||
@@ -1146,7 +1173,7 @@ pub async fn retry_phase(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(
|
||||
serde_json::json!({ "reset": true, "reopened_phases": reopened }),
|
||||
serde_json::json!({ "reset": true, "reopened_phases": reopened, "cleared_captures": cleared }),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user