Skip to content

On Linux the hub and the coder's browser cannot reach the app running in the sandbox #71

Closed

Description

@brianjfox

What happened. After the first build finished, the Live Preview stayed on "Starting the app" indefinitely, while its log showed the server and Vite had started. The coder had finished and was waiting on the person to try the app, which they could not open.

Why. On Linux, @anthropic-ai/sandbox-runtime wraps every command in bubblewrap with --unshare-net, so each sandboxed command has its own network namespace and its own loopback. The dev app started by executor.app() in apps/hub/src/builds/posix-sandbox.ts listens at http://127.0.0.1:<CLIENT_PORT> inside its namespace. The live-app host (apps/hub/src/live-app/host.ts, answers(app.url)) and the preview proxy (live-app/proxy.ts) fetch that URL from the hub's namespace, get connection refused, and report "starting" every second forever.

Verified in the Compose hub on 2026-11-02: the hub's /proc/1/ns/net was 4026532382; the live-app host's bun run dev and bun --watch src/index.ts were in 4026532474; the coder sandbox's own dev app was in 4026532739. Nothing in the hub's namespace listened on the app's ports.

The coder's Playwright browser is started by executor.browser() in a separate wrap, so it is in yet another namespace and cannot reach the app either; check-app passes because it starts the server and the browser inside one sandbox. On macOS the Seatbelt sandbox shares the host loopback, which is why this never showed.

Expected. The preview serves the app and the coder's browser can open it, on Linux as on macOS, without weakening the network policy.

Proposed fix. The executor already keeps a socket directory (sockets) that is writable inside every wrap, is bound into them, and that the hub can reach, and on Linux Unix sockets are allowed there (allowAllUnixSockets). Use it as the bridge, the same way the runtime bridges its own proxy ports:

  1. The app's wrap also runs socat UNIX-LISTEN:<sockets>/app.sock,fork,reuseaddr TCP:127.0.0.1:<CLIENT_PORT>, so the app is reachable from outside through the socket.
  2. DevApp carries the socket path; the live-app host's answers() and the proxy's fetch connect through it (Bun's fetch accepts unix), and the proxy's WebSocket relay for Vite HMR does the same.
  3. The browser's wrap runs the reverse relay, socat TCP-LISTEN:<CLIENT_PORT>,fork,reuseaddr UNIX-CONNECT:<sockets>/app.sock, so http://127.0.0.1:<CLIENT_PORT> works inside it too.

Related: #66 (sandbox fixes for the Compose hub), #67 (stale specialist runs).

Activity

  1. brianjfox commented on Nov 2, 2026

    @brianjfox
    ContributorAuthor

    Fixed on main by #81: the dev app is relayed through a Unix socket between the sandbox's network namespaces, so the hub, the browser and the coder's tools all reach it. The Compose port range that completes the preview is #91.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions