fix(missions): stop three launch failures from passing as success
A verification run against the deployed stack found a chain mission whose phase 0 reported `completed` with zero files, no commit error and no push error — indistinguishable from a phase that correctly had nothing to do. Three separate defects had to line up, each of them the same shape: a failure sharing its representation with a legitimate negative result. 1. `pin_agent_workspaces` embedded the whole config in one `sh -c` argv. That works until the file grows — config gains a block per provisioned claw — then fails with `argument list too long`. Now written through the tar upload API, which has no argv limit, so the failure mode is gone rather than merely further away. 2. A failed pin was logged "(continuing)". Without the pin, agents write to their sandboxes and the committer finds nothing in /mission/repo — the mission cannot deliver, so the launch now fails where someone is still looking. The restart that applies the pin is fatal for the same reason. 3. `capture_phase_diff_at` swallowed `git diff` failures with `unwrap_or_default`, so an unreadable base landed `empty: true, files_changed: 0` — byte-identical to an honest no-op. The error is now recorded as `diff_error`, and an empty patch that came from a failed diff is no longer trusted to mean an unchanged tree. Adds scripts/verify-mission-delivery.sh, which found #1 and #2 on its first real run. Its probes are fail-closed: no placeholder values, a self-test that proves the uid probe can detect the split it looks for, and FAIL-NORUN for a scenario that never executed. Its own first version had this bug too — a `die` inside `$(...)` exited the subshell, so a run that could not authenticate printed "all checks passed" and exited 0. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bb274d08c6
commit
1253595ba7
@@ -228,31 +228,35 @@ pub async fn on_launch(
|
||||
// is a PathBuf the prop-schema won't expose — see provision_claw), so
|
||||
// we patch the shared config file directly on the per-mission runtime
|
||||
// container. The daemon picks it up on the same reload that surfaces
|
||||
// the freshly-provisioned claws for the run. Non-fatal: without the
|
||||
// pin, agents still write (to the sandbox) but the committer can't
|
||||
// find the changes in /mission/repo.
|
||||
// the freshly-provisioned claws for the run.
|
||||
//
|
||||
// FATAL, deliberately. This was "non-fatal: agents still write (to the
|
||||
// sandbox) but the committer can't find the changes in /mission/repo" —
|
||||
// which is to say, the mission runs to completion and delivers nothing.
|
||||
// Mission `019fcf62` did exactly that: the pin failed with `argument list
|
||||
// too long`, one line of stderr scrolled past, and phase 0 reported
|
||||
// `completed` with zero files, no commit error and no push error. A launch
|
||||
// that cannot bind its agents to the repo has no path to delivering work,
|
||||
// so it must fail at launch where someone is still looking.
|
||||
if !provisioned_claws.is_empty() && mission_gateway.is_some() {
|
||||
if let Some(mp) = crate::mission_runtime::MissionRuntimeProvisioner::from_env() {
|
||||
match mp
|
||||
.pin_agent_workspaces(mission_id, &provisioned_claws, "/mission/repo")
|
||||
mp.pin_agent_workspaces(mission_id, &provisioned_claws, "/mission/repo")
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
// The daemon reads config ONCE at boot and never re-reads
|
||||
// the file, so the pin is invisible until it restarts. Its
|
||||
// agents were created through its own config API, so they
|
||||
// are already persisted to the file and survive the
|
||||
// restart; the pairing code is re-minted on every launch.
|
||||
if let Err(e) = mp.restart_container(mission_id).await {
|
||||
eprintln!(
|
||||
"mission_orchestrator: restart runtime for {mission_id} failed (continuing, workspace pin will not apply): {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
"mission_orchestrator: pin workspaces for mission {mission_id} failed (continuing): {e}"
|
||||
),
|
||||
}
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"could not pin agent workspaces to /mission/repo ({e}) — the mission \
|
||||
would run with its agents writing to their sandboxes, delivering nothing"
|
||||
)
|
||||
})?;
|
||||
// The daemon reads config ONCE at boot and never re-reads the
|
||||
// file, so the pin is invisible until it restarts. Its agents were
|
||||
// created through its own config API, so they are already
|
||||
// persisted to the file and survive the restart; the pairing code
|
||||
// is re-minted on every launch. Equally fatal: an unrestarted
|
||||
// daemon is an unpinned daemon.
|
||||
mp.restart_container(mission_id).await.map_err(|e| {
|
||||
format!("could not restart the runtime to apply the workspace pin: {e}")
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user