vercel/ai · error · CodeModeProtocolError

CODE_MODE_PROTOCOL_ERROR

CODE_MODE_PROTOCOL_ERROR

Error message

Code mode continuation envelope is malformed.

What it means

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.

Source

Thrown at packages/code-mode/src/continuation-capability.ts:99

export function verifyCodeModeContinuation(
  continuation: CodeModeContinuation,
  security: CodeModeContinuationSecurityOptions = {},
): void {
  if (
    typeof continuation !== 'object' ||
    continuation === null ||
    continuation.version !== 2 ||
    typeof continuation.js !== 'string' ||
    typeof continuation.outerToolCallId !== 'string' ||
    !Array.isArray(continuation.toolNames) ||
    !continuation.toolNames.every(name => typeof name === 'string') ||
    typeof continuation.token !== 'string' ||
    continuation.token.length === 0 ||
    !Array.isArray(continuation.pendingInterruptions) ||
    continuation.pendingInterruptions.length === 0 ||
    !Array.isArray(continuation.resolutions)
  ) {
    throw new CodeModeProtocolError(
      'Code mode continuation envelope is malformed.',
    );
  }
  assertAuthShape(continuation.auth);
  const now = Date.now();
  if (continuation.auth.expiresAtMs < now) {
    throw new CodeModeProtocolError('Code mode continuation has expired.', {
      expiresAtMs: continuation.auth.expiresAtMs,
      now,
    });
  }
  if (continuation.auth.issuedAtMs > now + 60_000) {
    throw new CodeModeProtocolError(
      'Code mode continuation was issued in the future.',
      { issuedAtMs: continuation.auth.issuedAtMs, now },
    );
  }

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Keep and reuse the exact CodeModeContinuation object returned by the interrupt, without modifications
  2. Check the object has version: 2, non-empty string token, and non-empty pendingInterruptions array before calling
  3. Re-serialize with a lossless format and confirm no field stripping/redaction happens before verification
  4. If upgrading from an older schema, re-create the continuation rather than passing legacy objects
  5. Use hasValidCodeModeContinuationCapability() to test validity without throwing

Example fix

// before
const stored = JSON.parse(await redis.get(id));
await continueCodeModeInterrupt(stored.continuation); // token stripped for size
// after
const continuation = JSON.parse(await redis.get(id));
if (
  continuation?.version !== 2 ||
  typeof continuation.token !== 'string' ||
  !continuation.pendingInterruptions?.length
) {
  throw new Error('continuation payload incomplete');
}
await continueCodeModeInterrupt(continuation);
Defensive patterns

Strategy: type-guard

Validate before calling

function looksLikeContinuation(v: unknown): boolean {
  const c = v as any;
  return (
    typeof c === 'object' && c !== null &&
    c.version === 2 &&
    typeof c.js === 'string' &&
    typeof c.outerToolCallId === 'string' &&
    Array.isArray(c.toolNames) &&
    typeof c.token === 'string' && c.token.length > 0 &&
    Array.isArray(c.pendingInterruptions) && c.pendingInterruptions.length > 0 &&
    Array.isArray(c.resolutions)
  );
}
if (!looksLikeContinuation(continuation)) throw new Error('continuation envelope incomplete');

Type guard

function isWellFormedContinuation(v: unknown): v is CodeModeContinuation {
  const c = v as any;
  return (
    typeof v === 'object' && v !== null &&
    c.version === 2 &&
    typeof c.js === 'string' &&
    typeof c.outerToolCallId === 'string' &&
    Array.isArray(c.toolNames) &&
    typeof c.token === 'string' && c.token.length > 0 &&
    Array.isArray(c.pendingInterruptions) && c.pendingInterruptions.length > 0 &&
    Array.isArray(c.resolutions)
  );
}

Try / catch

import { hasValidCodeModeContinuationCapability } from '.../continuation-capability.js';
if (!hasValidCodeModeContinuationCapability(continuation)) {
  throw new Error('continuation is malformed, expired, or has an invalid signature');
}
await continueCodeModeInterrupt(continuation);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/bbc4041a7203fb03. Report an issue: GitHub.