research_container: attach bridge network so per-team egress works
Per-team containers were spawned onto clawmates_core only. That
network is Internal=true on gw-04 (no default gateway to the host's
default route), so any egress attempt — including the daemon's
own claude/gemini/groq API calls — fails with FailedToOpenSocket
and the turn times out.
The shared clawmates-runtime is attached to BOTH clawmates_core AND
the default bridge (that's how it can hit api.anthropic.com); the
per-team containers were missing the second network.
Fix: after start_container succeeds, best-effort connect the
container to `bridge` too. Both spawn() and spawn_loop() call the
same helper. Idempotent — a 403 from Docker on repeat-attach ("already
on network") is silently ignored.
Verified out-of-band on the current stuck research team container:
- Manually `docker network connect bridge research-<id>-team`
- `docker exec ... claude --print "reply only: ok"` → returned "ok"
- Auth + egress both working, so next iteration should complete.
Sequence of pipeline fixes finally converging:
1. materialize_topic_loops didn't fire burst → fixed by
fire_initial_burst_if_set
2. research iteration skipped clone/spawn → fixed by
prepare_topic_runtime
3. graph parse failed → fixed by build_topic_graph_json
4. bind-mount perms wrong → chown 65532:65532
5. server image missing git → debian:12-slim base
6. daemon required pairing → prewrite_daemon_config
7. daemon rejected unknown agents → template config from shared runtime
8. THIS: no external egress → attach bridge post-start
This commit is contained in:
@@ -258,9 +258,37 @@ pub async fn spawn(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("start {name}: {e}"))?;
|
.map_err(|e| format!("start {name}: {e}"))?;
|
||||||
|
|
||||||
|
// Attach the default `bridge` network AFTER start so the container
|
||||||
|
// has external egress. Without this the team can join
|
||||||
|
// clawmates_core (Internal=true on gw-04) but can't reach
|
||||||
|
// api.anthropic.com — every LLM call fails with
|
||||||
|
// FailedToOpenSocket and the run times out.
|
||||||
|
attach_external_bridge(docker, &name).await;
|
||||||
|
|
||||||
Ok(SpawnedContainer { name, gateway_url })
|
Ok(SpawnedContainer { name, gateway_url })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Best-effort attach the container to the default `bridge` docker
|
||||||
|
/// network so it can reach the public internet. Silent on the "already
|
||||||
|
/// attached" case (repeat spawns / restarts). Logs any real failure
|
||||||
|
/// with the container name so a broken network isn't invisible.
|
||||||
|
async fn attach_external_bridge(docker: &Docker, name: &str) {
|
||||||
|
use bollard::network::ConnectNetworkOptions;
|
||||||
|
let opts: ConnectNetworkOptions<String> = ConnectNetworkOptions {
|
||||||
|
container: name.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match docker.connect_network("bridge", opts).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(bollard::errors::Error::DockerResponseServerError {
|
||||||
|
status_code: 403, ..
|
||||||
|
}) => {
|
||||||
|
// "endpoint already exists on network" — idempotent re-attach.
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("attach_external_bridge({name}): {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Poll the team gateway's `/health` endpoint until it 200s or the
|
/// Poll the team gateway's `/health` endpoint until it 200s or the
|
||||||
/// deadline passes. Called before firing turns against a freshly-spawned
|
/// deadline passes. Called before firing turns against a freshly-spawned
|
||||||
/// container so the executor doesn't try to pair against a not-yet-
|
/// container so the executor doesn't try to pair against a not-yet-
|
||||||
@@ -437,6 +465,10 @@ pub async fn spawn_loop(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("start {name}: {e}"))?;
|
.map_err(|e| format!("start {name}: {e}"))?;
|
||||||
|
|
||||||
|
// Same reason as `spawn` — clawmates_core is Internal=true; without
|
||||||
|
// bridge the loop container can't reach LLM APIs.
|
||||||
|
attach_external_bridge(docker, &name).await;
|
||||||
|
|
||||||
Ok(SpawnedContainer { name, gateway_url })
|
Ok(SpawnedContainer { name, gateway_url })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user