fix(smolmachines): forward guest env on every exec + chown /home/node
Two issues kept claude's TUI from drawing after launch: 1. smolvm pack remaps OCI-layer ownership to the host invoker's uid (501 on macOS) instead of preserving the image's USER node (uid 1000). /home/node ends up owned by some uid that doesn't exist in the VM, so when claude runs as node it can't appendFileSync to ~/.claude.json on startup — fails with ENOENT and the TUI hangs. Fix: chown -R node:node /home/node after machine_start, before provision. 2. smolvm machine_create -e sets env on PID 1 but it doesn't propagate to fresh exec process trees (verified empirically: `smolvm machine exec -- printenv` shows none of the machine_create env vars). Claude was running with no HTTPS_PROXY / CLAUDE_CODE_OAUTH_TOKEN / NODE_EXTRA_CA_CERTS, so even the auth-validation step bailed silently. Fix: thread `guest_env` through to the SmolmachinesBottle handle and re-pass every entry via `-e K=V` on every machine_exec call (interactive claude and shell exec both). Also fills in the same `CLAUDE_CODE_OAUTH_TOKEN=egress- placeholder` + telemetry-off env the docker backend's forwarded_env carries, plus the NODE_EXTRA_CA_CERTS / SSL_CERT_FILE / REQUESTS_CA_BUNDLE trust trio. Verified end-to-end on Docker Desktop / macOS: claude's TUI renders cleanly with the bypass-permissions banner. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -91,11 +91,18 @@ def resolve_plan(
|
||||
|
||||
# Agent's env. IP literals; no DNS resolution inside the guest
|
||||
# (TSI allowlist contains only `<bundle_ip>/32` — no resolver).
|
||||
# TLS trust env trio (NODE_EXTRA_CA_CERTS / SSL_CERT_FILE /
|
||||
# REQUESTS_CA_BUNDLE) points at Debian's
|
||||
# update-ca-certificates output bundle — provision_ca writes
|
||||
# the per-bottle MITM CA there at launch time.
|
||||
guest_env: dict[str, str] = {
|
||||
**bottle.env,
|
||||
"HTTPS_PROXY": f"http://{bundle_ip}:{_BUNDLE_PIPELOCK_PORT}",
|
||||
"HTTP_PROXY": f"http://{bundle_ip}:{_BUNDLE_PIPELOCK_PORT}",
|
||||
"NO_PROXY": "localhost,127.0.0.1",
|
||||
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt",
|
||||
"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt",
|
||||
"REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt",
|
||||
}
|
||||
if bottle.git:
|
||||
guest_env["GIT_GATE_URL"] = (
|
||||
@@ -124,6 +131,19 @@ def resolve_plan(
|
||||
egress_dir.mkdir(parents=True, exist_ok=True)
|
||||
egress_plan = Egress().prepare(bottle, slug, egress_dir)
|
||||
|
||||
# Claude-code refuses to start without *something* it
|
||||
# recognises as a credential. When the bottle has an egress
|
||||
# route carrying the `claude_code_oauth` role marker, egress
|
||||
# strips + re-injects the real Authorization header on the
|
||||
# outbound leg using a token held in egress's own environ — so
|
||||
# the agent gets a non-secret placeholder here (matches the
|
||||
# docker backend's forwarded_env logic in
|
||||
# claude_bottle/backend/docker/prepare.py).
|
||||
if any("claude_code_oauth" in r.roles for r in egress_plan.routes):
|
||||
guest_env["CLAUDE_CODE_OAUTH_TOKEN"] = "egress-placeholder"
|
||||
guest_env.setdefault("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
|
||||
guest_env.setdefault("DISABLE_ERROR_REPORTING", "1")
|
||||
|
||||
supervise_plan = None
|
||||
if bottle.supervise:
|
||||
supervise_dir = supervise_state_dir(slug)
|
||||
|
||||
Reference in New Issue
Block a user