paperclipai/paperclip · error

Invalid bridge body.

Error message

Invalid bridge body.

What it means

decodeSandboxBridgeBody parses a SandboxCallbackBridgeBody envelope received from the sandbox bridge. Before any size or encoding checks, it requires a truthy envelope object whose `body` field is a string; anything else (null/undefined envelope, body missing, or body of the wrong type) throws this error, guarding against corrupt or malformed queue messages.

Solutions

  1. Log/inspect the raw message that produced the envelope; confirm it deserialized into `{ body: string, bodyEncoding?: 'utf8'|'base64' }`.
  2. Fix the producer side so it calls encodeSandboxBridgeBody (which always emits a string `body`).
  3. Add a guard at the consumer to skip/dead-letter messages failing `typeof envelope?.body === 'string'` instead of crashing.
  4. Check for version skew between queue producers and the gateway embedding the decoder; align both on the current envelope contract.

Example fix

// before
const out = decodeSandboxBridgeBody(JSON.parse(raw), maxBodyBytes); // raw was '"hello"' -> body undefined
// after
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed.body !== "string") {
  deadLetter(raw); return;
}
const out = decodeSandboxBridgeBody(parsed, maxBodyBytes);
Defensive patterns

Strategy: type-guard

Validate before calling

function isDecodableBridgeBody(v: unknown): v is { body: string; bodyEncoding?: "utf8" | "base64" } {
  return typeof v === "object" && v !== null && typeof (v as any).body === "string"
    && ([(v as any).bodyEncoding, undefined].every(e => e === undefined || e === "utf8" || e === "base64"));
}

Type guard

function isBridgeEnvelope(v: unknown): v is SandboxCallbackBridgeBody {
  return !!v && typeof v === "object" && typeof (v as SandboxCallbackBridgeBody).body === "string";
}

Try / catch

try {
  const out = decodeSandboxBridgeBody(envelope, maxBodyBytes);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid bridge body.") {
    // dead-letter/inspect the raw message; it is not a valid envelope
  } else throw err;
}

Prevention

When it happens

Trigger: Calling decodeSandboxBridgeBody(envelope, maxBodyBytes) with a null/undefined envelope, an envelope where `body` is undefined/null/number/object, or a message produced by a producer that wrote a shape incompatible with SandboxCallbackBridgeBody (e.g. raw string instead of {body, bodyEncoding}).

Common situations: Queue peers on mismatched versions writing different envelope shapes (the bodyEncoding field is itself optional for legacy peers); a consumed message that failed JSON parsing upstream and left a placeholder value; a test or gateway hand-crafting the envelope with `{ body: 123 }` or omitting `body`.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/9d59a07b0ad48a1c. Report an issue: GitHub.

Appendix: source

Thrown at packages/adapter-utils/src/sandbox-callback-bridge-body.ts:19

export interface SandboxCallbackBridgeBody {
  body: string;
  /** Omitted by older queue peers, whose bodies are UTF-8 text. */
  bodyEncoding?: "utf8" | "base64";
}

/** JSON can escape each input byte as six characters. Metadata is bounded too. */
export function sandboxBridgeEnvelopeLimit(maxBodyBytes: number): number {
  return 6 * maxBodyBytes + 64 * 1024;
}

export function encodeSandboxBridgeBody(body: string | Buffer, maxBodyBytes: number): SandboxCallbackBridgeBody {
  if (Buffer.byteLength(body) > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
  return Buffer.isBuffer(body) ? { body: body.toString("base64"), bodyEncoding: "base64" } : { body };
}

/** Self-contained so the same decoder can be embedded in the remote gateway. */
export function decodeSandboxBridgeBody(envelope: SandboxCallbackBridgeBody, maxBodyBytes: number): Buffer {
  if (!envelope || typeof envelope.body !== "string") throw new Error("Invalid bridge body.");
  if (envelope.bodyEncoding === undefined || envelope.bodyEncoding === "utf8") {
    if (Buffer.byteLength(envelope.body, "utf8") > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
    return Buffer.from(envelope.body, "utf8");
  }
  if (envelope.bodyEncoding !== "base64") throw new Error("Unsupported bridge body encoding.");
  const value = envelope.body;
  if (value.length > 4 * Math.ceil(maxBodyBytes / 3)) throw new Error("Bridge body exceeded the configured size limit.");
  // Buffer.from is permissive; reject malformed input before allocating bytes.
  if (value.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(value) || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
    throw new Error("Invalid bridge base64 body.");
  }
  const bytes = Buffer.from(value, "base64");
  if (bytes.length > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
  if (bytes.toString("base64") !== value) throw new Error("Invalid bridge base64 body.");
  return bytes;
}

export function sandboxBridgeBodyCodecSource(): string {

View on GitHub (pinned to 3f1d897a7c)