paperclipai/paperclip · error
Cloudflare sandbox bridge streaming response ended without a
Error message
Cloudflare sandbox bridge streaming response ended without a completion event.
What it means
Thrown by `consumeExecuteEventStream` (bridge-client.ts:269) when the SSE stream ends (reader `done`) without the bridge ever emitting a `complete` event carrying the `CloudflareBridgeExecuteResponse`. Every streaming exec is contractually terminated by a `complete` event with exit code/stdout/stderr; its absence means the stream closed prematurely.
Source
Thrown at packages/plugins/sandbox-providers/cloudflare/src/bridge-client.ts:269
if (event.event === "complete") {
result = JSON.parse(event.data) as CloudflareBridgeExecuteResponse;
continue;
}
if (event.event === "error") {
const payload = JSON.parse(event.data) as { error?: unknown };
const message = typeof payload.error === "string" && payload.error.trim().length > 0
? payload.error
: "Cloudflare sandbox bridge streaming command failed.";
throw new Error(message);
}
}
if (done) break;
}
if (result) return result;
throw new Error("Cloudflare sandbox bridge streaming response ended without a completion event.");
}
export function createCloudflareBridgeClient(options: BridgeClientOptions) {
const { config } = options;
const apiPrefix = "/api/paperclip-sandbox/v1";
return {
health(extraHeaders?: BridgeClientHeaders): Promise<CloudflareBridgeHealthResponse> {
return requestJson<CloudflareBridgeHealthResponse>(config, `${apiPrefix}/health`, { method: "GET" }, extraHeaders);
},
probe(body: CloudflareBridgeProbeRequest, extraHeaders?: BridgeClientHeaders): Promise<CloudflareBridgeProbeResponse> {
return requestJson<CloudflareBridgeProbeResponse>(
config,
`${apiPrefix}/probe`,
{ method: "POST", body: JSON.stringify(body) },
extraHeaders,
);View on GitHub (pinned to 67001ec6eb)
Solutions
- Retry the exec; transient stream termination is often recoverable.
- Re-acquire/resume the lease if the sandbox may have been evicted (verifySentinel / 409 handling).
- For long-silent commands, ensure the bridge heartbeat (15s keepalives) is reaching the client, and consider non-streaming exec for short commands.
Defensive patterns
Strategy: retry
Type guard
function isStreamPrematureClose(e: unknown): boolean {
return e instanceof Error && /ended without a completion event/.test(e.message);
} Try / catch
try {
await client.execute(body, headers, { onOutput });
} catch (err) {
if (isStreamPrematureClose(err)) {
await client.resumeLease({ providerLeaseId }, headers); // recover if evicted
await client.execute(body, headers, { onOutput }); // one retry
} else throw err;
} Prevention
- Resume the lease after a premature close (the sandbox may have been evicted).
- Use keepAlive + heartbeats to keep silent SSE commands alive at the edge.
- Prefer non-streaming for short commands to avoid edge idle timeouts.
When it happens
Trigger: A streaming `/exec` call where the SSE connection terminates (Worker evicted, connection drop, heartbeat gap exceeding edge idle timeout, or the stream closed after only stdout/stderr events) before the `complete` event is sent.
Common situations: Cloudflare Worker CPU/wall-clock limit hit mid-command, the sandbox was evicted during execution, an edge idle timeout closed the SSE stream during a silent command, or the bridge crashed before emitting complete.
Related errors
- Cloudflare sandbox bridge streaming response had no body.
- Cloudflare sandbox bridge streaming command failed.
- Cloudflare sandbox bridge request failed with HTTP ${respons
- Cloudflare sandbox bridge request timed out after ${requestT
- ${action} timed out: ${result.stderr.trim()}
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/67e3d3ae8fbd5e21.
Report an issue: GitHub.