Summary
The exposePreview tool in @tanstack/ai-sandbox-cloudflare (preview-tool) resolves the preview URL with a bare sandbox.tunnels.get(port). tunnels.get in @cloudflare/sandbox is idempotent per port: it returns the cached tunnel record from DO storage without verifying that the cloudflared process — or the server behind it — still exists. The record only clears on container restart.
Failure mode (observed on a live deploy)
- Agent starts a dev server on port N, calls
exposePreview(N) → tunnel minted, URL works.
- The dev server (or cloudflared) dies later in the thread; the agent restarts the server.
- Agent calls
exposePreview(N) again → tunnels.get returns the same dead URL from storage.
- User opens it → 502 at the trycloudflare edge (
Unable to reach the origin service). From the agent's point of view the tool succeeded, so it confidently re-shares a dead link — there is no signal anywhere that anything is wrong.
This is deterministic: any container whose previewed process restarts while the container itself stays warm will reproduce it.
Suggested fix
Verify end to end before returning a URL, roughly:
- In-container probe — e.g.
sandbox.exec("curl -s -o /dev/null -w '%{http_code}' --max-time 5 http://127.0.0.1:PORT/"). This also catches the wrong-port case where a dev server scan-forwards from its requested port.
- Edge probe — fetch the tunnel URL; any status < 500 means the origin answered, 502/530 is the stale signature.
- On stale —
tunnels.destroy(port) then a fresh tunnels.get(port), with a short retry window for fresh-tunnel edge/DNS propagation, and a note in the tool result telling the agent the OLD url is dead so it re-shares the new one.
- On failure, return an actionable error ("nothing listening on port N inside the sandbox") instead of a URL that 502s.
We've been running exactly this shape as a drop-in replacement for the package tool and it closed the failure completely; happy to upstream it if you want a PR.
Environment
@cloudflare/sandbox 0.12.4 (SANDBOX_TRANSPORT: rpc), do-drives mode, quick tunnels (no PREVIEW_HOSTNAME)
Summary
The
exposePreviewtool in@tanstack/ai-sandbox-cloudflare(preview-tool) resolves the preview URL with a baresandbox.tunnels.get(port).tunnels.getin@cloudflare/sandboxis idempotent per port: it returns the cached tunnel record from DO storage without verifying that the cloudflared process — or the server behind it — still exists. The record only clears on container restart.Failure mode (observed on a live deploy)
exposePreview(N)→ tunnel minted, URL works.exposePreview(N)again →tunnels.getreturns the same dead URL from storage.Unable to reach the origin service). From the agent's point of view the tool succeeded, so it confidently re-shares a dead link — there is no signal anywhere that anything is wrong.This is deterministic: any container whose previewed process restarts while the container itself stays warm will reproduce it.
Suggested fix
Verify end to end before returning a URL, roughly:
sandbox.exec("curl -s -o /dev/null -w '%{http_code}' --max-time 5 http://127.0.0.1:PORT/"). This also catches the wrong-port case where a dev server scan-forwards from its requested port.tunnels.destroy(port)then a freshtunnels.get(port), with a short retry window for fresh-tunnel edge/DNS propagation, and a note in the tool result telling the agent the OLD url is dead so it re-shares the new one.We've been running exactly this shape as a drop-in replacement for the package tool and it closed the failure completely; happy to upstream it if you want a PR.
Environment
@cloudflare/sandbox0.12.4 (SANDBOX_TRANSPORT: rpc), do-drives mode, quick tunnels (no PREVIEW_HOSTNAME)