{"record":{"id":"bbc4041a7203fb03","repo":"vercel/ai","slug":"code-mode-protocol-error-bbc404","errorCode":"CODE_MODE_PROTOCOL_ERROR","errorMessage":"Code mode continuation envelope is malformed.","messagePattern":"Code mode continuation envelope is malformed\\.","errorType":"exception","errorClass":"CodeModeProtocolError","httpStatus":null,"severity":"error","filePath":"packages/code-mode/src/continuation-capability.ts","lineNumber":99,"sourceCode":"export function verifyCodeModeContinuation(\n  continuation: CodeModeContinuation,\n  security: CodeModeContinuationSecurityOptions = {},\n): void {\n  if (\n    typeof continuation !== 'object' ||\n    continuation === null ||\n    continuation.version !== 2 ||\n    typeof continuation.js !== 'string' ||\n    typeof continuation.outerToolCallId !== 'string' ||\n    !Array.isArray(continuation.toolNames) ||\n    !continuation.toolNames.every(name => typeof name === 'string') ||\n    typeof continuation.token !== 'string' ||\n    continuation.token.length === 0 ||\n    !Array.isArray(continuation.pendingInterruptions) ||\n    continuation.pendingInterruptions.length === 0 ||\n    !Array.isArray(continuation.resolutions)\n  ) {\n    throw new CodeModeProtocolError(\n      'Code mode continuation envelope is malformed.',\n    );\n  }\n  assertAuthShape(continuation.auth);\n  const now = Date.now();\n  if (continuation.auth.expiresAtMs < now) {\n    throw new CodeModeProtocolError('Code mode continuation has expired.', {\n      expiresAtMs: continuation.auth.expiresAtMs,\n      now,\n    });\n  }\n  if (continuation.auth.issuedAtMs > now + 60_000) {\n    throw new CodeModeProtocolError(\n      'Code mode continuation was issued in the future.',\n      { issuedAtMs: continuation.auth.issuedAtMs, now },\n    );\n  }\n","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/code-mode/src/continuation-capability.ts#L81-L117","documentation":"verifyCodeModeContinuation validates the structural envelope of a CodeModeContinuation before checking its auth metadata and HMAC signature. It throws this CodeModeProtocolError when required fields are missing or mistyped: version !== 2, non-string js/outerToolCallId/token, empty token, missing/empty pendingInterruptions array, or a non-array resolutions field. This guards against replaying corrupted, truncated, or hand-forged continuation objects.","triggerScenarios":"Calling continueCodeModeInterrupt / prepareContinuation with a continuation that was JSON-round-tripped with fields dropped; constructing the object manually with missing pendingInterruptions or an empty array; persisting continuations and loading old-schema objects (version !== 2); passing null/undefined or a redacted object where token or pendingInterruptions were stripped for logging.","commonSituations":"Storing continuations in a DB/Redis and a schema migration (v1 vs v2) changed field names; serializers that drop arrays or undefined fields; redacting `token` in logs and then reusing the redacted copy; middleware transforming the payload between services.","solutions":["Keep and reuse the exact CodeModeContinuation object returned by the interrupt, without modifications","Check the object has version: 2, non-empty string token, and non-empty pendingInterruptions array before calling","Re-serialize with a lossless format and confirm no field stripping/redaction happens before verification","If upgrading from an older schema, re-create the continuation rather than passing legacy objects","Use hasValidCodeModeContinuationCapability() to test validity without throwing"],"exampleFix":"// before\nconst stored = JSON.parse(await redis.get(id));\nawait continueCodeModeInterrupt(stored.continuation); // token stripped for size\n// after\nconst continuation = JSON.parse(await redis.get(id));\nif (\n  continuation?.version !== 2 ||\n  typeof continuation.token !== 'string' ||\n  !continuation.pendingInterruptions?.length\n) {\n  throw new Error('continuation payload incomplete');\n}\nawait continueCodeModeInterrupt(continuation);","handlingStrategy":"type-guard","validationCode":"function looksLikeContinuation(v: unknown): boolean {\n  const c = v as any;\n  return (\n    typeof c === 'object' && c !== null &&\n    c.version === 2 &&\n    typeof c.js === 'string' &&\n    typeof c.outerToolCallId === 'string' &&\n    Array.isArray(c.toolNames) &&\n    typeof c.token === 'string' && c.token.length > 0 &&\n    Array.isArray(c.pendingInterruptions) && c.pendingInterruptions.length > 0 &&\n    Array.isArray(c.resolutions)\n  );\n}\nif (!looksLikeContinuation(continuation)) throw new Error('continuation envelope incomplete');","typeGuard":"function isWellFormedContinuation(v: unknown): v is CodeModeContinuation {\n  const c = v as any;\n  return (\n    typeof v === 'object' && v !== null &&\n    c.version === 2 &&\n    typeof c.js === 'string' &&\n    typeof c.outerToolCallId === 'string' &&\n    Array.isArray(c.toolNames) &&\n    typeof c.token === 'string' && c.token.length > 0 &&\n    Array.isArray(c.pendingInterruptions) && c.pendingInterruptions.length > 0 &&\n    Array.isArray(c.resolutions)\n  );\n}","tryCatchPattern":"import { hasValidCodeModeContinuationCapability } from '.../continuation-capability.js';\nif (!hasValidCodeModeContinuationCapability(continuation)) {\n  throw new Error('continuation is malformed, expired, or has an invalid signature');\n}\nawait continueCodeModeInterrupt(continuation);","preventionTips":["Persist and reuse the continuation object exactly as returned; never redact or strip fields","Check version === 2 when loading persisted continuations across SDK upgrades","Serialize losslessly (full JSON) when transferring between services","Use hasValidCodeModeContinuationCapability() as a cheap pre-check before continuing"],"tags":["code-mode","protocol","continuation-token","schema-validation-failed"],"backgroundTag":"schema-validation-failed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}