fix(missions): a microvm mission needs no team, and two checks required one

Found by running one: the mission was created with runtime_kind='microvm' and
then refused to launch with a bare 400, because draft→running requires a
materializable team. Past that, `launch_phase` returns early when a phase has no
matching teams — so even with the launch allowed, the phase would have sat
`pending` forever while the log said only "no matching teams", and the executor
would never have been reached.

Neither check applies to this path: microvm_executor runs the agent CLI directly
in the VM, so there is no claw graph to materialise. Satisfying the checks by
attaching a team template would have provisioned claws that never run.

The repo checkout still happens — the VM needs the repository.

461 tests pass, clippy clean.
This commit is contained in:
Omar Sobh
2026-08-05 16:39:48 -07:00
parent c9b7d8b6ca
commit 0a9747091f
2 changed files with 17 additions and 2 deletions
+7 -1
View File
@@ -374,7 +374,13 @@ async fn launch_phase(
.await .await
.map_err(|e| format!("query mission teams: {e}"))?; .map_err(|e| format!("query mission teams: {e}"))?;
if team_rows.is_empty() { // A microVM mission has no teams and needs none: its agent is a `claude -p`
// inside a VM, not a graph of ZeroClaw claws. Without this exemption the
// phase would sit `pending` forever while the log said only "no matching
// teams" — the executor below would never be reached at all.
//
// The checkout below still runs, because the VM needs the repository.
if team_rows.is_empty() && p.runtime_kind != "microvm" {
eprintln!( eprintln!(
"phase_runner: mission {mission_id} phase {phase_id} ({kind}) has no matching teams — skipping (staying pending)" "phase_runner: mission {mission_id} phase {phase_id} ({kind}) has no matching teams — skipping (staying pending)"
); );
+10 -1
View File
@@ -854,7 +854,16 @@ pub async fn set_status(
.any(|v| v.as_array().map(|a| !a.is_empty()).unwrap_or(false)) .any(|v| v.as_array().map(|a| !a.is_empty()).unwrap_or(false))
}) })
.unwrap_or(false); .unwrap_or(false);
if prior.team_id.is_none() && prior.team_template_id.is_none() && !has_phase_teams { // A microVM mission materialises no team — `microvm_executor` runs the
// agent CLI directly in the VM — so requiring one would reject the launch
// of a perfectly well-formed mission, and satisfying it would provision
// claws that never run.
let needs_team = prior.runtime_kind != "microvm";
if needs_team
&& prior.team_id.is_none()
&& prior.team_template_id.is_none()
&& !has_phase_teams
{
eprintln!( eprintln!(
"mission {id}: launch rejected — no team_id, no team_template_id, no config.phase_teams" "mission {id}: launch rejected — no team_id, no team_template_id, no config.phase_teams"
); );