vercel/ai · warning
The streamable value has been slow to update. This may be a
Error message
The streamable value has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.
What it means
In development mode, createStreamableValue arms a timer that fires this warning if the streamable value hasn't been updated or closed within HANGING_STREAM_WARNING_TIME_MS. It signals that a stream created on the server was never finished, likely because .done(), .update(), or .append() were not called, or the value update path stalled.
Source
Thrown at packages/rsc/src/streamable-value/create-streamable-value.ts:153
function assertStream(method: string) {
if (closed) {
throw new Error(method + ': Value stream is already closed.');
}
if (locked) {
throw new Error(
method + ': Value stream is locked and cannot be updated.',
);
}
}
let warningTimeout: NodeJS.Timeout | undefined;
function warnUnclosedStream() {
if (process.env.NODE_ENV === 'development') {
if (warningTimeout) {
clearTimeout(warningTimeout);
}
warningTimeout = setTimeout(() => {
console.warn(
'The streamable value has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.',
);
}, HANGING_STREAM_WARNING_TIME_MS);
}
}
warnUnclosedStream();
function createWrapped(initialChunk?: boolean): StreamableValue<T, E> {
// This makes the payload much smaller if there're mutative updates before the first read.
let init: Partial<StreamableValue<T, E>>;
if (currentError !== undefined) {
init = { error: currentError };
} else {
if (currentPatchValue && !initialChunk) {
init = { diff: currentPatchValue };
} else {
init = { curr: currentValue };View on GitHub (pinned to 69428b1f8b)
Solutions
- Ensure every code path calls streamableValue.done() (use try/finally).
- Call .update() promptly or create the value lazily right before results are available.
- Call .error(...) on failure paths instead of leaving the stream open.
- Increase/ignore the warning in dev if the operation is legitimately long-running.
Example fix
// before
const value = createStreamableValue();
const result = await doSlowWork();
value.update(result); // may never run on throw
// after
const value = createStreamableValue();
try {
value.update(await doSlowWork());
} finally {
value.done();
} Defensive patterns
Strategy: try-catch
Try / catch
const value = createStreamableValue();
try {
value.update(await work());
} catch (e) {
value.error(e);
throw e;
} finally {
value.done();
} Prevention
- Always pair createStreamableValue() with done()/error() in finally
- Keep stream lifetime short; create the value right before async work
- Test RSC handlers with simulated failures to catch unclosed streams
When it happens
Trigger: Calling createStreamableValue() on the server and neither updating nor calling .done() within the warning window; slow async work between value creation and first update; forgetting to close the stream when an error occurs.
Common situations: RSC/generative-UI handlers that create a streamable value but an exception skips the .done() call; long-running server operations exceeding the hang threshold; forgotten close on early-return code paths.
Related errors
- The streamable UI has been slow to update. This may be a bug
- ${method}: Value stream is already closed.
- ${method}: Value stream is locked and cannot be updated.
- .append(): The current value is not a string. Received: ${ty
- .append(): The value is not a string. Received: ${typeof val
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/71e02d20d63f92bf.
Report an issue: GitHub.