{"record":{"id":"9d59a07b0ad48a1c","repo":"paperclipai/paperclip","slug":"invalid-bridge-body","errorCode":null,"errorMessage":"Invalid bridge body.","messagePattern":"Invalid bridge body\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/sandbox-callback-bridge-body.ts","lineNumber":19,"sourceCode":"export interface SandboxCallbackBridgeBody {\n  body: string;\n  /** Omitted by older queue peers, whose bodies are UTF-8 text. */\n  bodyEncoding?: \"utf8\" | \"base64\";\n}\n\n/** JSON can escape each input byte as six characters. Metadata is bounded too. */\nexport function sandboxBridgeEnvelopeLimit(maxBodyBytes: number): number {\n  return 6 * maxBodyBytes + 64 * 1024;\n}\n\nexport function encodeSandboxBridgeBody(body: string | Buffer, maxBodyBytes: number): SandboxCallbackBridgeBody {\n  if (Buffer.byteLength(body) > maxBodyBytes) throw new Error(\"Bridge body exceeded the configured size limit.\");\n  return Buffer.isBuffer(body) ? { body: body.toString(\"base64\"), bodyEncoding: \"base64\" } : { body };\n}\n\n/** Self-contained so the same decoder can be embedded in the remote gateway. */\nexport function decodeSandboxBridgeBody(envelope: SandboxCallbackBridgeBody, maxBodyBytes: number): Buffer {\n  if (!envelope || typeof envelope.body !== \"string\") throw new Error(\"Invalid bridge body.\");\n  if (envelope.bodyEncoding === undefined || envelope.bodyEncoding === \"utf8\") {\n    if (Buffer.byteLength(envelope.body, \"utf8\") > maxBodyBytes) throw new Error(\"Bridge body exceeded the configured size limit.\");\n    return Buffer.from(envelope.body, \"utf8\");\n  }\n  if (envelope.bodyEncoding !== \"base64\") throw new Error(\"Unsupported bridge body encoding.\");\n  const value = envelope.body;\n  if (value.length > 4 * Math.ceil(maxBodyBytes / 3)) throw new Error(\"Bridge body exceeded the configured size limit.\");\n  // Buffer.from is permissive; reject malformed input before allocating bytes.\n  if (value.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(value) || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {\n    throw new Error(\"Invalid bridge base64 body.\");\n  }\n  const bytes = Buffer.from(value, \"base64\");\n  if (bytes.length > maxBodyBytes) throw new Error(\"Bridge body exceeded the configured size limit.\");\n  if (bytes.toString(\"base64\") !== value) throw new Error(\"Invalid bridge base64 body.\");\n  return bytes;\n}\n\nexport function sandboxBridgeBodyCodecSource(): string {","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/adapter-utils/src/sandbox-callback-bridge-body.ts#L1-L37","documentation":"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.","triggerScenarios":"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}).","commonSituations":"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`.","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."],"exampleFix":"// before\nconst out = decodeSandboxBridgeBody(JSON.parse(raw), maxBodyBytes); // raw was '\"hello\"' -> body undefined\n// after\nconst parsed = JSON.parse(raw);\nif (!parsed || typeof parsed.body !== \"string\") {\n  deadLetter(raw); return;\n}\nconst out = decodeSandboxBridgeBody(parsed, maxBodyBytes);","handlingStrategy":"type-guard","validationCode":"function isDecodableBridgeBody(v: unknown): v is { body: string; bodyEncoding?: \"utf8\" | \"base64\" } {\n  return typeof v === \"object\" && v !== null && typeof (v as any).body === \"string\"\n    && ([(v as any).bodyEncoding, undefined].every(e => e === undefined || e === \"utf8\" || e === \"base64\"));\n}","typeGuard":"function isBridgeEnvelope(v: unknown): v is SandboxCallbackBridgeBody {\n  return !!v && typeof v === \"object\" && typeof (v as SandboxCallbackBridgeBody).body === \"string\";\n}","tryCatchPattern":"try {\n  const out = decodeSandboxBridgeBody(envelope, maxBodyBytes);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Invalid bridge body.\") {\n    // dead-letter/inspect the raw message; it is not a valid envelope\n  } else throw err;\n}","preventionTips":["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."],"tags":["validation","deserialization","sandbox-bridge","message-format"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}