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

  1. Ensure only one code path owns the streamable value and its lock
  2. Call lock() only after all updates and appends are finished
  3. Use separate streamable values for independent producers
  4. 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

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


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