paperclipai/paperclip · warning

Chat SDK runtime is shutting down

Error message

Chat SDK runtime is shutting down

What it means

assertReplacementCurrent is the guard applied when installing a replacement chat SDK runtime for an endpoint. If the runtime is shutting down, it throws 'Chat SDK runtime is shutting down'; if the recorded replacement generation for the endpoint no longer matches, it throws the 'was superseded' error. This ensures only the newest replacement wins and none are installed during teardown.

Source

Thrown at server/src/services/chat-sdk-runtime.ts:3183

    if (!previous) return false;
    this.endpoints.delete(endpointId);
    this.retiringEndpoints.set(endpointId, previous);
    await previous.shutdown();
    this.retiringEndpoints.delete(endpointId);
    return true;
  }

  private nextReplacementGeneration(endpointId: string): number {
    const next = (this.replacementGenerations.get(endpointId) ?? 0) + 1;
    this.replacementGenerations.set(endpointId, next);
    return next;
  }

  private assertReplacementCurrent(
    endpointId: string,
    generation: number,
  ): void {
    if (this.shuttingDown) throw new Error("Chat SDK runtime is shutting down");
    if (this.replacementGenerations.get(endpointId) !== generation) {
      throw new Error(
        `Chat SDK runtime replacement for endpoint ${endpointId} was superseded`,
      );
    }
  }

  async replaceEndpoint(
    options: CreateChatSdkEndpointRuntimeOptions,
  ): Promise<ChatSdkEndpointRuntime> {
    if (this.shuttingDown) throw new Error("Chat SDK runtime is shutting down");
    const generation = this.nextReplacementGeneration(options.endpointId);
    return await this.enqueueLifecycle(options.endpointId, async () => {
      this.assertReplacementCurrent(options.endpointId, generation);
      await this.retireEndpoint(options.endpointId);
      this.assertReplacementCurrent(options.endpointId, generation);
      const next = createChatSdkEndpointRuntime(options);
      this.endpoints.set(options.endpointId, next);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Check runtime shutdown state before scheduling a replacement; skip replacements during teardown
  2. Re-read the latest generation for the endpoint and re-issue the replacement if superseded
  3. Serialize replacement installation per endpoint (queue or lock) to avoid generation races
  4. Treat the error as a benign no-op when it occurs during shutdown and let the new runtime take over

Example fix

// before
runtime.installReplacement(endpointId, generation);
// after
const latest = runtime.getReplacementGeneration(endpointId);
if (latest === generation && !runtime.isShuttingDown) {
  runtime.installReplacement(endpointId, generation);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (runtime.isShuttingDown) return; // skip replacement during teardown

Try / catch

try { installReplacement(endpointId, generation); } catch (e) { if ((e as Error).message.startsWith('Chat SDK runtime')) { /* benign: superseded or shutting down; skip */ } else throw e; }

Prevention

When it happens

Trigger: Installing a runtime replacement concurrently with runtime shutdown, or a replacement whose generation counter no longer equals the latest registered generation for that endpointId (an even newer replacement already claimed the slot).

Common situations: Service shutdown racing an in-flight endpoint swap; two restart/replacement flows racing for the same endpoint; a restart routine issuing a second generation before the first is installed.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/bd222170d08f8fa4. Report an issue: GitHub.