{"record":{"id":"d8a2a9ec882b2d33","repo":"vercel/ai","slug":"method-value-stream-is-already-closed","errorCode":null,"errorMessage":"${method}: Value stream is already closed.","messagePattern":"(.+?): Value stream is already closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/rsc/src/streamable-value/create-streamable-value.ts","lineNumber":137,"sourceCode":"   * updated by the user.\n   */\n  [STREAMABLE_VALUE_INTERNAL_LOCK]: boolean;\n};\n\nfunction createStreamableValueImpl<T = any, E = any>(initialValue?: T) {\n  let closed = false;\n  let locked = false;\n  let resolvable = createResolvablePromise<StreamableValue<T, E>>();\n\n  let currentValue = initialValue;\n  let currentError: E | undefined;\n  let currentPromise: typeof resolvable.promise | undefined =\n    resolvable.promise;\n  let currentPatchValue: StreamablePatch;\n\n  function assertStream(method: string) {\n    if (closed) {\n      throw new Error(method + ': Value stream is already closed.');\n    }\n    if (locked) {\n      throw new Error(\n        method + ': Value stream is locked and cannot be updated.',\n      );\n    }\n  }\n\n  let warningTimeout: NodeJS.Timeout | undefined;\n  function warnUnclosedStream() {\n    if (process.env.NODE_ENV === 'development') {\n      if (warningTimeout) {\n        clearTimeout(warningTimeout);\n      }\n      warningTimeout = setTimeout(() => {\n        console.warn(\n          'The streamable value has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.',\n        );","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/rsc/src/streamable-value/create-streamable-value.ts#L119-L155","documentation":"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.","triggerScenarios":"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.","commonSituations":"Background tasks or event handlers that keep emitting after close(); calling done() then append(); React Server Component streams finalized before an async continuation runs.","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"],"exampleFix":"// before\nstreamable.update(next);\nstreamable.close();\nstreamable.update(more); // throws\n// after\nstreamable.update(next);\nstreamable.update(more);\nstreamable.close();","handlingStrategy":"validation","validationCode":"let isClosed = false;\nfunction safeUpdate(streamable, value) {\n  if (isClosed) return; // skip emissions after close\n  streamable.update(value);\n}\n// set isClosed = true wherever you call streamable.close()","typeGuard":"function canEmit(streamable) {\n  return streamable && !streamable.closed; // track your own closed flag if not exposed\n}","tryCatchPattern":"try {\n  streamable.update(value);\n} catch (e) {\n  if (!/Value stream is already closed/.test(String(e.message))) throw e;\n  // else: emission after close — safe to ignore or log\n}","preventionTips":["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"],"tags":["streaming","rsc","lifecycle","closed-stream"],"backgroundTag":"stream-already-closed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}