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
- Emit 'reasoning-start' before 'reasoning-end' with identical ids, exactly once each.
- Track open reasoning part ids on the server to avoid double ends.
- Ensure stream persistence/replay includes the start chunk or restarts parts on resume.
- 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
- Emit reasoning-end exactly once per reasoning part and only after reasoning-start.
- Track open reasoning parts server-side to prevent duplicate end chunks.
- Use shared id variables across start/delta/end emissions.
- Validate chunk ordering in integration tests for custom streams.
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
- Received reasoning-delta for missing reasoning part with ID
- Received text-delta for missing text part with ID "${chunk.i
- Received text-end for missing text part with ID "${chunk.id}
- Received tool-input-delta for missing tool call with ID "${c
- No tool invocation found for tool call ID "${toolCallId}".
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/451ec7428798c211.
Report an issue: GitHub.