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

  1. Retry the exec; transient stream termination is often recoverable.
  2. Re-acquire/resume the lease if the sandbox may have been evicted (verifySentinel / 409 handling).
  3. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/67e3d3ae8fbd5e21. Report an issue: GitHub.