From b84523324e06659fb12805c27c3e3d1d45c0fd0b Mon Sep 17 00:00:00 2001 From: elf-mouse Date: Wed, 5 Aug 2026 22:18:10 -0700 Subject: [PATCH] fix(sandbox): install docker-cli and add container DNS fallback routing for local sandboxes This commit resolves docker socket permission issues and container-to-container networking for local sandboxes in containerized deployments: 1. `deploy/core/Dockerfile`: - Install `docker-cli` package and allow root execution to access mounted `/var/run/docker.sock`. 2. `src/sandbox/local-sandbox.ts`: - Fall back between `127.0.0.1`, `host.docker.internal`, and container hostname `http://${name}:8080` in `daemon()` to ensure reliable container-to-container execution across all environments. --- deploy/core/Dockerfile | 2 +- src/sandbox/local-sandbox.ts | 30 ++++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/deploy/core/Dockerfile b/deploy/core/Dockerfile index 9be91f94..df5d0aa6 100644 --- a/deploy/core/Dockerfile +++ b/deploy/core/Dockerfile @@ -1,6 +1,6 @@ FROM node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd -RUN apk add --no-cache ca-certificates curl git git-daemon +RUN apk add --no-cache ca-certificates curl git git-daemon docker-cli WORKDIR /app diff --git a/src/sandbox/local-sandbox.ts b/src/sandbox/local-sandbox.ts index ce8c97b4..bae13196 100644 --- a/src/sandbox/local-sandbox.ts +++ b/src/sandbox/local-sandbox.ts @@ -165,14 +165,28 @@ export function createLocalSandbox(workspace: WorkspaceStore, opts: LocalSandbox timeoutMs?: number, signal?: AbortSignal, ): Promise<{ status: number; text: string }> { - const port = await resolvePort(name); - const signals = [AbortSignal.timeout(timeoutMs ?? 30_000), ...(signal ? [signal] : [])]; - const res = await fetchImpl(`http://127.0.0.1:${port}${path}`, { - method: body === undefined ? "GET" : "POST", - ...(body === undefined ? {} : { body: JSON.stringify(body), headers: { "content-type": "application/json" } }), - signal: AbortSignal.any(signals), - }); - return { status: res.status, text: await res.text() }; + const port = await resolvePort(name).catch(() => null); + const targets: string[] = []; + if (port) { + targets.push(`http://127.0.0.1:${port}`); + targets.push(`http://host.docker.internal:${port}`); + } + targets.push(`http://${name}:${AGENT_PORT}`); + let lastErr: unknown; + for (const base of targets) { + try { + const signals = [AbortSignal.timeout(timeoutMs ?? 5000), ...(signal ? [signal] : [])]; + const res = await fetchImpl(`${base}${path}`, { + method: body === undefined ? "GET" : "POST", + ...(body === undefined ? {} : { body: JSON.stringify(body), headers: { "content-type": "application/json" } }), + signal: AbortSignal.any(signals), + }); + return { status: res.status, text: await res.text() }; + } catch (e) { + lastErr = e; + } + } + throw lastErr ?? new Error(`local sandbox ${name}: unreachable`); } async function waitDaemon(name: string): Promise {