paperclipai/paperclip · error · CloudflareBridgeError

Cloudflare sandbox bridge request failed with HTTP ${respons

Error message

Cloudflare sandbox bridge request failed with HTTP ${response.status}.

What it means

Thrown by the Cloudflare bridge client's `requestJson` (bridge-client.ts:122-128) as a `CloudflareBridgeError` when the bridge returns a non-2xx HTTP status and the JSON error body either has no `message` field or an empty/whitespace one. The literal `HTTP ${response.status}` is the fallback so the caller still sees the status code; a non-empty server `message` would override it.

Source

Thrown at packages/plugins/sandbox-providers/cloudflare/src/bridge-client.ts:122

  path: string,
  init: RequestInit,
  extraHeaders: BridgeClientHeaders = {},
): Promise<T> {
  const controller = new AbortController();
  const requestTimeoutMs = resolveRequestTimeoutMs(config, path, init);
  const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
  const baseUrl = config.bridgeBaseUrl.replace(/\/+$/, "");

  try {
    const response = await fetch(`${baseUrl}${path}`, {
      ...init,
      headers: buildHeaders(config, extraHeaders),
      signal: controller.signal,
    });
    const body = await parseJson(response);
    if (!response.ok) {
      const errorBody = isRecord(body) ? body as BridgeErrorBody : {};
      throw new CloudflareBridgeError({
        status: response.status,
        code: typeof errorBody.error === "string" ? errorBody.error : null,
        message:
          typeof errorBody.message === "string" && errorBody.message.trim().length > 0
            ? errorBody.message
            : `Cloudflare sandbox bridge request failed with HTTP ${response.status}.`,
        details: errorBody.details,
      });
    }
    return body as T;
  } catch (error) {
    if (error instanceof CloudflareBridgeError) throw error;
    if ((error as { name?: string } | null)?.name === "AbortError") {
      throw new Error(
        `Cloudflare sandbox bridge request timed out after ${requestTimeoutMs}ms.`,
      );
    }
    throw error;

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Inspect the `CloudflareBridgeError.status` and `.code` fields (not just the message) to determine the real failure class.
  2. For 401, verify `config.bridgeAuthToken` matches the bridge's `BRIDGE_AUTH_TOKEN`.
  3. For 409 on resume, re-acquire the lease instead of resuming.
  4. For 400, validate the request body shape before sending.
Defensive patterns

Strategy: try-catch

Type guard

import { CloudflareBridgeError } from "@paperclipai/cloudflare-bridge-client";
function isBridgeError(e: unknown, status?: number): e is CloudflareBridgeError {
  return e instanceof CloudflareBridgeError && (status === undefined || e.status === status);
}

Try / catch

try {
  await client.health();
} catch (err) {
  if (err instanceof CloudflareBridgeError) {
    if (err.status === 401) refreshBridgeToken();
    else if (err.status === 400) fixRequestBody();
    else throw err;
  } else throw err;
}

Prevention

When it happens

Trigger: Any JSON bridge call (`/health`, `/probe`, `/leases/*`, non-streaming `/exec`) gets a 4xx/5xx from the bridge Worker — e.g. 401 (bad bearer token), 400 `invalid_request`, 409 `sandbox_state_lost`, 404 route mismatch — and the response body omits a usable message string.

Common situations: Wrong/missing `bridgeAuthToken` (401), malformed request body (400), resuming a lease whose sandbox state was evicted (409), or a bridge deployment that returns plain status codes without a JSON message field.

Related errors


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