vercel/ai · error · Error
${method}: Value stream is locked and cannot be updated.
Error message
${method}: Value stream is locked and cannot be updated. What it means
assertStream throws this when a mutating method (update, append, error, done) is called on a locked streamable value. Locking prevents any further updates while still allowing clients to keep reading the value (locking is distinct from closing).
Source
Thrown at packages/rsc/src/streamable-value/create-streamable-value.ts:140
};
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()`.',
);
}, HANGING_STREAM_WARNING_TIME_MS);
}
}View on GitHub (pinned to 69428b1f8b)
Solutions
- Ensure only one code path owns the streamable value and its lock
- Call lock() only after all updates and appends are finished
- Use separate streamable values for independent producers
- Route further output through a different stream instead of the locked one
Example fix
// before
streamable.update('partial');
streamable.lock();
streamable.update('final'); // throws
// after
streamable.update('partial');
streamable.update('final');
streamable.lock(); Defensive patterns
Strategy: validation
Validate before calling
let isLocked = false;
function safeUpdate(streamable, value) {
if (isLocked) throw new Error('Refusing update: stream locked');
streamable.update(value);
}
// set isLocked = true wherever you call streamable.lock() Type guard
function canUpdate(streamable) {
return streamable && !streamable.locked; // track your own lock flag if not exposed
} Try / catch
try {
streamable.update(value);
} catch (e) {
if (!/Value stream is locked/.test(String(e.message))) throw e;
// else: route output elsewhere or log
} Prevention
- Ensure a single owner calls lock(), and only after all updates/appends complete
- Document lock/close ownership when sharing streamable values across modules
- Prefer separate streamable values for independent producers
When it happens
Trigger: Calling .update()/.append()/.error()/.done() after .lock() was invoked — typically two code paths both owning the same streamable value where one locks it.
Common situations: Multiple server components/actions sharing one streamable value; one path locks for final rendering while another (e.g. a background job or a redirect) still tries to update.
Related errors
- ${method}: Value stream is already closed.
- .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/b531bd91491038b3.
Report an issue: GitHub.