4f136a9932
Claude hung on outbound network calls under CLAUDE_BOTTLE_BACKEND=smolmachines: Unable to connect to API (FailedToOpenSocket) Root cause: the PRD-0023 design pinned the bundle at a docker bridge IP (192.168.X.2) and set the smolvm guest's TSI allowlist to `<bundle-ip>/32`. On native Linux this works — host shares the docker bridge's network namespace, TSI's syscall impersonation reaches the bridge IP directly. On Docker Desktop (macOS), the daemon runs in its own Linux VM and docker bridge IPs aren't reachable from macOS networking, so the smolvm guest's TSI requests die "Network is unreachable" before they hit pipelock. Fix: publish each agent-facing bundle daemon's port on host loopback (-p 127.0.0.1::PORT), discover the random host-side ports after start, and route the agent through `127.0.0.1:<host port>` instead of the bridge IP. macOS loopback is the surface Docker Desktop's gvproxy forwards into the daemon's VM, so the chain (guest TSI -> macOS loopback -> daemon VM port-forward -> bundle container) works on both Docker Desktop and native Linux. Concrete changes: - BundleLaunchSpec: add `ports_to_publish` so start_bundle adds `-p 127.0.0.1::PORT` for the agent-facing ports (pipelock always; git-gate when upstreams declared; supervise when enabled). Egress's port stays bundle-internal. - sidecar_bundle.bundle_host_port(): wrap `docker port <bundle> <container_port>/tcp` so launch can look up the random host-side mapping after start. - launch.py: discover the host ports, build URLs of the form `http://127.0.0.1:<host port>` / `git://127.0.0.1:<host port>`, stamp onto guest_env + new agent_*_url fields on the plan. - launch.py: TSI allow_cidrs flips to `["127.0.0.1/32"]`. The bundle IP is no longer the agent's target. - prepare.py: stop synthesizing HTTPS_PROXY / GIT_GATE_URL / MCP_SUPERVISE_URL at prepare time — launch owns those now (the values depend on a port docker hasn't assigned yet). - provision_git: gate_host from plan.agent_git_gate_host. - provision_supervise: URL from plan.agent_supervise_url. End-to-end verified on Docker Desktop / macOS: guest dials pipelock through TSI, pipelock forwards to api.anthropic.com, the API responds with 401 (i.e. it received the request). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
2.1 KiB
Python
61 lines
2.1 KiB
Python
"""Supervise sidecar provisioning inside a running smolmachines
|
|
bottle (PRD 0023 chunk 4d; PRD 0013 supervise plane).
|
|
|
|
Registers the per-bottle supervise sidecar as an HTTP MCP server
|
|
in the agent's claude-code config so the agent discovers the
|
|
stuck-recovery MCP tools (pipelock-block, capability-block) at
|
|
startup.
|
|
|
|
Mirrors `backend.docker.provision.supervise` — same `claude mcp
|
|
add` call, just dispatched via `smolvm machine exec` instead of
|
|
`docker exec`, and against `<bundle_ip>:<port>` instead of the
|
|
short `supervise` alias (no DNS in the TSI-allowlisted guest)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ....log import info, warn
|
|
from .. import smolvm as _smolvm
|
|
from ..bottle_plan import SmolmachinesBottlePlan
|
|
|
|
|
|
_SUPERVISE_MCP_NAME = "supervise"
|
|
|
|
|
|
def provision_supervise(plan: SmolmachinesBottlePlan, target: str) -> None:
|
|
"""Run `claude mcp add` inside the guest to register the
|
|
supervise sidecar in claude-code's user config. No-op when
|
|
bottle.supervise is False.
|
|
|
|
The URL is the agent-side endpoint launch.py populated after
|
|
bundle bringup — `http://127.0.0.1:<host port>/` rather than
|
|
the bundle's docker bridge IP, because that bridge isn't
|
|
reachable from the smolvm guest on macOS.
|
|
|
|
Failure is logged but not fatal: the bottle still works (you
|
|
just can't call supervise tools from the agent until the entry
|
|
is added manually). The operator sees the warning at launch."""
|
|
if plan.supervise_plan is None:
|
|
return
|
|
url = plan.agent_supervise_url
|
|
info(f"registering supervise MCP server in agent claude config → {url}")
|
|
r = _smolvm.machine_exec(
|
|
target,
|
|
[
|
|
"claude", "mcp", "add",
|
|
"--scope", "user",
|
|
"--transport", "http",
|
|
_SUPERVISE_MCP_NAME,
|
|
url,
|
|
],
|
|
)
|
|
if r.returncode != 0:
|
|
warn(
|
|
f"`claude mcp add supervise` failed (exit {r.returncode}): "
|
|
f"{(r.stderr or r.stdout or '').strip()}. Inside the bottle, "
|
|
f"register manually with: "
|
|
f"claude mcp add --scope user --transport http supervise {url}"
|
|
)
|
|
|
|
|
|
__all__ = ["provision_supervise"]
|