research: teardown per-topic container on publish + delete (P1)
ci / gates (push) Successful in 7s
ci / rust (push) Failing after 10s
ci / frontend (push) Successful in 26s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped

Path B spawned a per-topic team container on start_topic (research
runtime) but nothing ever stopped it. Containers accumulated on gw-04
across the lifetime of every topic — the earlier cleanup pass reclaimed
6.72 GB of these. Now the container is stopped + removed automatically
at the two terminal transitions:

- delete_topic — the topic row is gone, the container is meaningless.
- approve_publish (reviewing → publishing) — the research work is done.
  Artifact writing (queued as R1) reads from the durable run_events
  log, so it doesn't need a live runtime.

Wiring is a fire-and-forget teardown() helper in research_container.rs:
- Connects Docker via the same socket-proxy shim as spawn().
- Calls the existing stop() which is idempotent (404 = already gone,
  304 = already stopped, both treated as ok).
- Errors log with eprintln! and don't block the API response — a topic
  is done whether Docker is reachable or not. Dev machines without
  Docker running just log a debug line and return.

Path A (repo-only mount + shared clawmates_core network) is untouched
so there's no Traefik dynamic-file cleanup needed. If we ever add
per-topic Traefik routing, extend teardown() with the corresponding
file remove.

Follow-up: gw-04 will still have any legacy containers from before this
commit. One-shot `docker ps -a --filter "name=research-" -q | xargs -r
docker rm -f` on gw-04 is the manual sweep.
This commit is contained in:
Omar Sobh
2026-07-09 14:02:50 -07:00
parent 763b95a253
commit f60df36717
2 changed files with 32 additions and 0 deletions
+8
View File
@@ -543,6 +543,10 @@ pub async fn delete_topic(
Path(id): Path<Uuid>,
) -> Result<StatusCode, ApiError> {
cm_db::repo::research_topics::delete(&state.pool, id, user.workspace_id.as_uuid()).await?;
// Teardown the per-topic team container (Path B). Fire-and-forget:
// the topic row is already gone, so any Docker failure is a log-line
// problem, not an API-response problem.
crate::research_container::teardown(id).await;
Ok(StatusCode::NO_CONTENT)
}
@@ -885,6 +889,10 @@ async fn decide_publish(
"publishing",
)
.await?;
// The research work is done — tear down the per-topic team
// container. Artifact-writing (R1) doesn't need a live runtime;
// it reads run_events from the durable log.
crate::research_container::teardown(approval.topic_id).await;
}
Ok(StatusCode::NO_CONTENT)
}