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
@@ -67,7 +67,7 @@ pub async fn ensure_checkout(
|
||||
|
||||
let auth_url = with_ambient_auth(clone_url);
|
||||
if path.join(".git").exists() {
|
||||
fetch_and_reset(&path, default_branch).await?;
|
||||
fetch_and_reset(&path, default_branch, &auth_url).await?;
|
||||
} else {
|
||||
clone(&path, &auth_url).await?;
|
||||
}
|
||||
@@ -311,7 +311,11 @@ fn redact_token(s: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
async fn fetch_and_reset(path: &std::path::Path, branch: &str) -> Result<(), String> {
|
||||
async fn fetch_and_reset(
|
||||
path: &std::path::Path,
|
||||
branch: &str,
|
||||
auth_url: &str,
|
||||
) -> Result<(), String> {
|
||||
// A checkout cloned before delivery existed is shallow, and a shallow repo
|
||||
// cannot push a new branch. Deepen it once, here, rather than discovering
|
||||
// the problem at push time when there is work on the line. `--unshallow`
|
||||
@@ -324,7 +328,7 @@ async fn fetch_and_reset(path: &std::path::Path, branch: &str) -> Result<(), Str
|
||||
&path.display().to_string(),
|
||||
"fetch",
|
||||
"--unshallow",
|
||||
"origin",
|
||||
auth_url,
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
@@ -345,8 +349,14 @@ async fn fetch_and_reset(path: &std::path::Path, branch: &str) -> Result<(), Str
|
||||
),
|
||||
}
|
||||
}
|
||||
// Fetch from an explicitly authenticated URL rather than the stored
|
||||
// remote. `scrub_remote_credentials` strips the token out of
|
||||
// `.git/config` — the checkout is readable by agents running as root —
|
||||
// so `git fetch origin` has no credentials and fails with
|
||||
// "could not read Username". Building the URL here also means a rotated
|
||||
// token takes effect immediately instead of at the next clone.
|
||||
let fetch = Command::new("git")
|
||||
.args(["-C", &path.display().to_string(), "fetch", "origin", branch])
|
||||
.args(["-C", &path.display().to_string(), "fetch", auth_url, branch])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("spawn git fetch: {e}"))?;
|
||||
|
||||
Reference in New Issue
Block a user