topology/log-sse: resolve team container name for coding-loop runs
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 3m59s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m16s

The log SSE endpoint was always resolving the container name from a
research topic id (research-<topic_id>-team). For paired coding loops
that own a team_id, the actual container the topology worker spawns is
team-<team_id>-container (spawn_team) — not the research topic's one.
That mismatch produced a stream of 404s from Docker:

  Docker responded with status code 404: No such container:
  research-<topic_id>-team

Mirror topology_worker::try_team_gateway_url's precedence:
  1. run's loop has team_id → team-<team_id>-container
  2. run has research_topic_id → research-<topic_id>-team
  3. loop.source_research_topic_id (legacy paired flow) → same as 2
  4. else → clear error, no more 404 spam

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-18 19:51:02 -07:00
co-authored by Claude Opus 4.7
parent 82b5cf4385
commit 5bbab870fc
+31 -12
View File
@@ -497,9 +497,25 @@ pub async fn run_container_log_sse(
);
return;
}
// Precedence — must mirror topology_worker::try_team_gateway_url,
// which is what actually spawns the container:
// a) run's loop has team_id set → the team runtime spawned
// `team-<team_id>-container` (matches spawn_team). This is
// the paired-coding-loop path when the wizard picked
// "fresh coding team". Loops with a team_id do NOT reuse
// the research topic's container.
// b) run.research_topic_id set → per-topic research container
// `research-<topic_id>-team` (matches spawn).
// c) run's loop has source_research_topic_id (legacy paired
// flow, no team_id) → same as (b) via the topic.
// d) anything else → error with a clear message.
let loop_id = cm_db::repo::topology_runs::loop_id_for_run(&pool, id).await.ok().flatten();
let team_id = match loop_id {
Some(lid) => cm_db::repo::teams::team_for_loop(&pool, lid).await.ok().flatten(),
None => None,
};
let direct = cm_db::repo::topology_runs::research_topic_id(&pool, id).await.ok().flatten();
let via_loop = if direct.is_none() {
let loop_id = cm_db::repo::topology_runs::loop_id_for_run(&pool, id).await.ok().flatten();
let via_loop = if team_id.is_none() && direct.is_none() {
match loop_id {
Some(lid) => {
use sqlx::Row;
@@ -516,20 +532,23 @@ pub async fn run_container_log_sse(
None => None,
}
} else { None };
let topic_id = match direct.or(via_loop) {
Some(t) => t,
None => {
yield Ok::<Event, Infallible>(
Event::default().event("error").data(
"run has no bound research topic or paired-loop topic; container log unavailable",
),
);
return;
let container = if let Some(tid) = team_id {
crate::research_container::team_container_name_for(tid)
} else {
match direct.or(via_loop) {
Some(t) => crate::research_container::container_name_for(t),
None => {
yield Ok::<Event, Infallible>(
Event::default().event("error").data(
"run has no bound team, research topic, or paired-loop topic; container log unavailable",
),
);
return;
}
}
};
// 2) Docker handle.
let container = crate::research_container::container_name_for(topic_id);
let docker = match crate::research_container::connect() {
Ok(d) => d,
Err(e) => {