From 521b8dea101513460220f8004da36f99a49ec421 Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Wed, 5 Aug 2026 16:47:08 -0700 Subject: [PATCH] fix(missions): the third team gate, and a container a microvm mission never uses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit on_launch demanded a team template too — "pick teams in the wizard" — so a microvm mission still could not launch after the first two gates were exempted. Three separate places required a claw graph for a path that runs one `claude -p` inside a VM: routes::missions (draft→running), phase_runner::launch_phase (no matching teams → stay pending), and here. Returning before team materialisation rather than filtering its picks: claws that never run are not a cheaper version of the same thing, they are a runtime binding and a pairing code describing something nothing speaks to. Also stops provisioning the per-mission ZeroClaw container for a microvm mission. The first real run was observed starting one and leaving it holding a pairing code and ~3 GB of image for the life of a mission that never contacts it. 461 tests pass, clippy clean. --- crates/cm-api/src/mission_orchestrator.rs | 25 ++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/cm-api/src/mission_orchestrator.rs b/crates/cm-api/src/mission_orchestrator.rs index df54158..5da4aad 100644 --- a/crates/cm-api/src/mission_orchestrator.rs +++ b/crates/cm-api/src/mission_orchestrator.rs @@ -79,7 +79,12 @@ pub async fn on_launch( // The mission's own runtime endpoint. Claws MUST be provisioned against // THIS gateway, not the global one — see RuntimeProvisioner::for_gateway. let mut mission_gateway: Option = None; - if let Some(prov) = crate::mission_runtime::MissionRuntimeProvisioner::from_env() { + // Not for a microVM mission: the ZeroClaw daemon it would start is never + // spoken to, and it would sit holding a pairing code and ~3 GB of image for + // the life of the mission. Observed doing exactly that on the first real run. + if let Some(prov) = crate::mission_runtime::MissionRuntimeProvisioner::from_env() + .filter(|_| mission.runtime_kind != "microvm") + { match prov.ensure_container(mission_id).await { Ok(ec) => { mission_gateway = Some(ec.endpoint.clone()); @@ -115,6 +120,24 @@ pub async fn on_launch( ); } + // A microVM mission materialises no team. Its phases run as one `claude -p` + // inside a VM (`microvm_executor`), so there is no claw graph to provision — + // and demanding one rejected the launch of a well-formed mission with "pick + // teams in the wizard". This is the third of three team gates on a path that + // uses no teams; the other two are in `routes::missions` (draft→running) and + // `phase_runner::launch_phase` (no matching teams → stay pending). + // + // Returning before the picks below, not filtering them, because provisioning + // claws that never run is not a cheaper version of the same thing — it is a + // runtime binding and a pairing code describing something nothing uses. + if mission.runtime_kind == "microvm" { + eprintln!( + "mission_orchestrator: mission {mission_id} is a microvm mission — no team to \ + materialise; its phases execute in a VM" + ); + return Ok(None); + } + // Skip team materialization if already bound. if mission.team_id.is_some() { eprintln!(