vercel/ai · error · Error
${method}: Value stream is already closed.
Error message
${method}: Value stream is already closed. What it means
createStreamableValue's assertStream guard throws this when any mutating method (update, append, error, done) is called after the stream has been closed via .close(). Closed streams can no longer emit values to the client.
Source
Thrown at packages/rsc/src/streamable-value/create-streamable-value.ts:137
* updated by the user.
*/
[STREAMABLE_VALUE_INTERNAL_LOCK]: boolean;
};
function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
let closed = false;
let locked = false;
let resolvable = createResolvablePromise<StreamableValue<T, E>>();
let currentValue = initialValue;
let currentError: E | undefined;
let currentPromise: typeof resolvable.promise | undefined =
resolvable.promise;
let currentPatchValue: StreamablePatch;
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()`.',
);View on GitHub (pinned to 69428b1f8b)
Solutions
- Track stream lifecycle; stop emitting after close() is called
- Call done() or close() only once, at the very end of producing values
- Restructure so all emissions happen before closing (e.g. await all appends before done())
- Wrap emissions in a guard checking your own closed flag
Example fix
// before streamable.update(next); streamable.close(); streamable.update(more); // throws // after streamable.update(next); streamable.update(more); streamable.close();
Defensive patterns
Strategy: validation
Validate before calling
let isClosed = false;
function safeUpdate(streamable, value) {
if (isClosed) return; // skip emissions after close
streamable.update(value);
}
// set isClosed = true wherever you call streamable.close() Type guard
function canEmit(streamable) {
return streamable && !streamable.closed; // track your own closed flag if not exposed
} Try / catch
try {
streamable.update(value);
} catch (e) {
if (!/Value stream is already closed/.test(String(e.message))) throw e;
// else: emission after close — safe to ignore or log
} Prevention
- Call close()/done() exactly once, as the last statement of your streaming logic
- Wrap emissions in a lifecycle helper that no-ops after close
- Be careful with background tasks outliving the stream — cancel them before closing
When it happens
Trigger: Calling .update()/.append()/.error()/.done() on a streamable value after .close() was already invoked — often from code that continues running after a server action finishes or a duplicate code path closes the stream.
Common situations: Background tasks or event handlers that keep emitting after close(); calling done() then append(); React Server Component streams finalized before an async continuation runs.
Related errors
- ${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
- `getMutableAIState` must be called before returning from an
- ${method}: UI stream is already closed.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/d8a2a9ec882b2d33.
Report an issue: GitHub.