fix(missions): unblock the capture batch, and restore fetch auth
Two defects, both found by running a second real coding mission (019fc3ba)
after the first round of fixes. The agent created the file correctly this
time — `file_write` did its job — and capture still produced nothing.
**Head-of-line blocking.** `capture_phase_diff` returns `Ok(None)` when the
checkout is gone, and the caller treated that as success without recording
anything. The phase therefore stayed eligible forever, and because the batch
is bounded at five, five reaped phases from earlier test missions occupied
every slot permanently. A freshly finished coding phase, with its checkout
still on disk, was never reached — and nothing was logged, because nothing had
failed.
Fixed on both axes: an unreachable checkout now writes a `code_diff` marker
recording `captured: false` and why, so the row stops being selected; and the
batch orders newest-first, so live work is captured before archaeology. The
marker also distinguishes "this phase changed nothing" from "we lost the
checkout before looking", which an operator reading the mission needs to be
able to tell apart.
**Fetch lost its credentials.** `scrub_remote_credentials` (P1.1) strips the
token from `.git/config` so agents running as root cannot read it — but
`fetch_and_reset` fetched from the stored remote, which is now anonymous:
git fetch origin <branch> → exit 128:
fatal: could not read Username for 'https://git.redclaw.dev'
I accounted for push building a fresh authenticated URL and overlooked that
fetch needs one too. `fetch_and_reset` now takes the authenticated URL the
caller already computes, as does the `--unshallow` deepen. Stderr stays
redacted.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
409ca65ee7
commit
e089360ac8
@@ -317,6 +317,46 @@ pub fn parse_diffstat(stat: &str) -> (usize, usize, usize) {
|
||||
(files, ins, del)
|
||||
}
|
||||
|
||||
/// Mark a phase as impossible to capture, so it stops being selected.
|
||||
///
|
||||
/// A phase whose checkout has already been reaped can never be captured. It
|
||||
/// must still be recorded: the capture batch is bounded, and a row that stays
|
||||
/// eligible forever occupies a slot forever. Enough of them and no live
|
||||
/// mission is ever captured again — head-of-line blocking with a silent
|
||||
/// failure mode, which is how this was found.
|
||||
///
|
||||
/// The artifact is deliberately honest about *why* it is empty. "No changes"
|
||||
/// and "we lost the checkout before looking" are different facts, and an
|
||||
/// operator reading the mission needs to be able to tell them apart.
|
||||
pub async fn record_uncapturable(
|
||||
pool: &sqlx::PgPool,
|
||||
mission_id: Uuid,
|
||||
phase_id: Uuid,
|
||||
) -> Result<(), String> {
|
||||
let rel = format!("_outputs/{mission_id}/{phase_id}/diff.patch");
|
||||
cm_db::repo::missions::register_artifact(
|
||||
pool,
|
||||
cm_db::repo::missions::RegisterArtifact {
|
||||
mission_id,
|
||||
phase_id: Some(phase_id),
|
||||
path: &rel,
|
||||
kind: "code_diff",
|
||||
mime: Some("text/x-patch"),
|
||||
title: Some("Not captured — checkout unavailable"),
|
||||
generated_by_run: None,
|
||||
render_pdf: false,
|
||||
metadata: Some(json!({
|
||||
"empty": true,
|
||||
"captured": false,
|
||||
"reason": "the mission checkout was removed before the diff could be captured",
|
||||
})),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("register uncapturable marker: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user