vercel/ai · warning

[WorkflowChatTransport] Dropping orphan UI chunk (${orphanKi

Error message

[WorkflowChatTransport] Dropping orphan UI chunk (${orphanKind} for id "${orphanRef}") on resume — the resume position landed mid-part. The dropped chunk(s) reference a part whose start chunk wasn't in the resumed window. To preserve the full message, configure your stream endpoint to rewind to a step boundary before returning the readable. See: https://workflow.dev/docs/ai/resumable-streams#mid-part-resumes

What it means

When resuming a UI message stream, chunks can reference a message part whose start chunk was not included in the resumed window (an 'orphan' chunk). The transport drops such chunks so the UI isn't corrupted, and warns once (first occurrence only) that the resume landed mid-part, with a doc link on how to avoid it.

Source

Thrown at packages/workflow/src/workflow-chat-transport.ts:48

 * part from those directly), so all three mark the call id as seen.
 *
 * This is a best-effort safety net — it preserves only the parts that the
 * resumed window includes a `*-start` for. Server-side rewinding to a step
 * boundary is the proper fix when you want the full message preserved.
 */
type OrphanFilter = {
  shouldDrop: (chunk: UIMessageChunk) => boolean;
};

function createOrphanFilter(): OrphanFilter {
  const seenStartedIds = new Set<string>();
  const seenStartedToolCallIds = new Set<string>();
  let warnedOnce = false;

  function warnOnce(orphanKind: string, orphanRef: string) {
    if (warnedOnce) return;
    warnedOnce = true;
    console.warn(
      '[WorkflowChatTransport] Dropping orphan UI chunk ' +
        `(${orphanKind} for id "${orphanRef}") on resume — ` +
        'the resume position landed mid-part. The dropped chunk(s) ' +
        "reference a part whose start chunk wasn't in the resumed " +
        'window. To preserve the full message, configure your ' +
        'stream endpoint to rewind to a step boundary before ' +
        'returning the readable. See: ' +
        'https://workflow.dev/docs/ai/resumable-streams#mid-part-resumes',
    );
  }

  function shouldDrop(chunk: UIMessageChunk): boolean {
    switch (chunk.type) {
      case 'reset-step':
        seenStartedIds.clear();
        seenStartedToolCallIds.clear();
        return false;
      case 'text-start':

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Configure the stream endpoint to rewind to a step boundary before returning the readable, so a part-start chunk always begins the resumed window.
  2. Include the part-start chunks in the resumed stream window (start the stream earlier).
  3. Persist the full stream server-side and replay from the last complete step boundary.
  4. Accept the drop if losing the partially-started part is acceptable.
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: Calling reconnectToStream / resuming a chat where the server's reconnection endpoint returns a stream whose first chunks are continuation chunks (e.g. text-delta, tool-output updates) without the preceding part-start chunk (e.g. text-start, tool-input-start) for the same id.

Common situations: Custom stream endpoints that resume from an arbitrary chunk index rather than a step boundary; servers trimming persisted stream logs; clients reconnecting after a mid-part disconnect.

Related errors


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