vercel/ai · error · UIMessageStreamError

Received reasoning-end for missing reasoning part with ID "$

Error message

Received reasoning-end for missing reasoning part with ID "${chunk.id}". Ensure a "reasoning-start" chunk is sent before any "reasoning-end" chunks.

What it means

When handling 'reasoning-end', the processor requires an active reasoning part with the chunk id in `state.activeReasoningParts`. If none exists (never started or already ended), it throws this UIMessageStreamError. Reasoning parts must be opened before they can be closed.

Source

Thrown at packages/ai/src/ui/process-ui-message-stream.ts:523

                throw new UIMessageStreamError({
                  chunkType: 'reasoning-delta',
                  chunkId: chunk.id,
                  message:
                    `Received reasoning-delta for missing reasoning part with ID "${chunk.id}". ` +
                    `Ensure a "reasoning-start" chunk is sent before any "reasoning-delta" chunks.`,
                });
              }
              reasoningPart.text += chunk.delta;
              reasoningPart.providerMetadata =
                chunk.providerMetadata ?? reasoningPart.providerMetadata;
              write();
              break;
            }

            case 'reasoning-end': {
              const reasoningPart = state.activeReasoningParts[chunk.id];
              if (reasoningPart == null) {
                throw new UIMessageStreamError({
                  chunkType: 'reasoning-end',
                  chunkId: chunk.id,
                  message:
                    `Received reasoning-end for missing reasoning part with ID "${chunk.id}". ` +
                    `Ensure a "reasoning-start" chunk is sent before any "reasoning-end" chunks.`,
                });
              }
              reasoningPart.providerMetadata =
                chunk.providerMetadata ?? reasoningPart.providerMetadata;
              reasoningPart.state = 'done';
              delete state.activeReasoningParts[chunk.id];

              write();
              break;
            }

            case 'file':
            case 'reasoning-file': {

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Emit 'reasoning-start' before 'reasoning-end' with identical ids, exactly once each.
  2. Track open reasoning part ids on the server to avoid double ends.
  3. Ensure stream persistence/replay includes the start chunk or restarts parts on resume.
  4. Catch UIMessageStreamError for reasoning-end and treat as a no-op for already-closed parts.

Example fix

// before
writer.write({ type: 'reasoning-end', id: 'r1' }); // never started
// after
writer.write({ type: 'reasoning-start', id: 'r1' });
writer.write({ type: 'reasoning-delta', id: 'r1', delta: '...' });
writer.write({ type: 'reasoning-end', id: 'r1' });
Defensive patterns

Strategy: validation

Validate before calling

const openReasoning = new Set<string>();
function assertReasoningOpenBeforeEnd(id: string) {
  if (!openReasoning.has(id)) throw new Error(`reasoning-end without open reasoning-start for id ${id}`);
  openReasoning.delete(id);
}

Try / catch

try {
  await processUIMessageStream(...);
} catch (e) {
  if (e instanceof Error && /reasoning-end for missing reasoning part/.test(e.message)) {
    console.warn('Malformed stream: orphan reasoning-end', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: A 'reasoning-end' chunk arrives with an id lacking a matching open 'reasoning-start' — duplicate ends, id mismatch, or out-of-order chunks in custom streams.

Common situations: Server-side code emitting reasoning-end twice; reasoning start/end ids diverging after refactors; stream replays that skip the start chunk; third-party provider adapters generating malformed chunk sequences.

Related errors


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