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
- Log/inspect the raw message that produced the envelope; confirm it deserialized into `{ body: string, bodyEncoding?: 'utf8'|'base64' }`.
- Fix the producer side so it calls encodeSandboxBridgeBody (which always emits a string `body`).
- Add a guard at the consumer to skip/dead-letter messages failing `typeof envelope?.body === 'string'` instead of crashing.
- 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
- Always produce envelopes via encodeSandboxBridgeBody so `body` is a string.
- Validate queue messages with a schema/type guard before decoding.
- Pin compatible versions between sandbox producers and the gateway decoder.
- On parse failure of the raw message, dead-letter rather than fabricating a placeholder envelope.
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
- Bridge body exceeded the configured size limit.
- Invalid bridge base64 body.
- Unsupported bridge body encoding.
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
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)