fix(missions): the door read a field that is not set yet

`install_skills_door` took the container name from
`mission.runtime_container_name`, and `on_launch` loads the mission at the
top — before `ensure_container` runs and binds that field. So it was
always `None`, and the early return had no log, so the door simply never
installed and said nothing about it. Verified against a live mission: no
log line, no file in the container.

That is the same shape as the three hook bugs before it, which is a poor
excuse for repeating it. The name is derived from the mission id
(`container_name`) instead, guarded on `mission_gateway` being Some —
which is exactly the signal that `ensure_container` ran and that this
mission has its own container rather than the shared runtime.

Every remaining early return now logs.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018i9Ten1LU4jUr5d7TAWda9
This commit is contained in:
Omar Sobh
2026-08-21 11:51:33 -07:00
co-authored by Claude Opus 5
parent 73f5d71c55
commit 3aeee070b8
+20 -11
View File
@@ -346,7 +346,19 @@ pub async fn on_launch(
settings ({e}) — this mission's tool calls run unchecked"
);
}
install_skills_door(pool, user_id, &mission, p).await;
// Only when this mission got its OWN container — the shared runtime is
// not ours to reconfigure, and `mission_gateway` being Some is exactly
// the signal that `ensure_container` ran.
if mission_gateway.is_some() {
install_skills_door(
pool,
user_id,
mission_id,
&crate::mission_runtime::container_name(mission_id),
p,
)
.await;
}
}
let mut first_team_id: Option<Uuid> = None;
let mut provisioned_claws: Vec<cm_domain::AgentId> = Vec::new();
@@ -907,17 +919,14 @@ fn default_accent_for(slot: &str) -> &'static str {
async fn install_skills_door(
pool: &PgPool,
user_id: cm_domain::UserId,
mission: &cm_db::repo::missions::Mission,
mission_id: Uuid,
container: &str,
prov: &RuntimeProvisioner,
) {
let Some(container) = mission.runtime_container_name.as_deref() else {
return;
};
let Some(origin) = crate::container_tool_hooks::api_origin() else {
eprintln!(
"mission_orchestrator: no API origin for the skills door (set \
CLAWMATES_API_ORIGIN) — mission {} runs without it",
mission.id
CLAWMATES_API_ORIGIN) — mission {mission_id} runs without it"
);
return;
};
@@ -932,8 +941,7 @@ async fn install_skills_door(
Err(e) => {
eprintln!(
"mission_orchestrator: could not mint a skills token ({e}) — \
mission {} runs without the door",
mission.id
mission {mission_id} runs without the door"
);
return;
}
@@ -948,6 +956,7 @@ async fn install_skills_door(
let doc = crate::container_tool_hooks::mcp_document(&origin, &token);
let Some(path) = crate::container_tool_hooks::install_door(&docker, container, &doc).await
else {
// `install_door` already said why.
return;
};
if let Err(e) = prov.set_claude_cli_mcp_config(&path).await {
@@ -958,7 +967,7 @@ async fn install_skills_door(
return;
}
eprintln!(
"mission_orchestrator: skills door installed for mission {} ({origin}/mcp/skills)",
mission.id
"mission_orchestrator: skills door installed for mission {mission_id} \
({origin}/mcp/skills)"
);
}