paperclipai/paperclip · error

Chat SDK endpoint runtime was retired

Error message

Chat SDK endpoint runtime was retired

What it means

The Chat SDK runtime can be retired (shut down) while asynchronous work is still in flight. Every critical entry point calls assertNotRetired(); if the runtime has been retired, this error is thrown to prevent operating on a dead runtime (e.g. initialize() completing after shutdown).

Source

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

  async initialize(): Promise<void> {
    await this.initializeChat();
    this.assertNotRetired();
    if (this.provider === "discord" && this.discordGatewayEnabled) {
      this.startDiscordGateway();
    }
  }

  private async initializeChat(): Promise<void> {
    this.assertNotRetired();
    // The service still chooses when to initialize, after installing callback
    // context. Retirement owns the settlement of that exact SDK operation.
    this.initialization ??= this.chat.initialize();
    await this.initialization;
    this.assertNotRetired();
  }

  private assertNotRetired(): void {
    if (this.retired) throw new Error("Chat SDK endpoint runtime was retired");
  }

  private startDiscordGateway(): void {
    this.assertNotRetired();
    if (this.discordGatewayTask) return;
    const adapter = this.adapter as DiscordAdapter;
    if (typeof adapter.startGatewayListener !== "function") return;
    const abort = new AbortController();
    this.discordGatewayAbort = abort;
    this.discordGatewayTask = (async () => {
      let rapidRestartCount = 0;
      while (!abort.signal.aborted) {
        const sessionStartedAt = Date.now();
        let listener: Promise<unknown> | null = null;
        try {
          await adapter.startGatewayListener(
            {
              waitUntil: (task) => {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Check runtime lifecycle: do not call initialize/start methods after retire(); gate calls behind a readiness check.
  2. Serialize shutdown: await all in-flight initialization before retiring.
  3. Recreate a new runtime instance if the endpoint is needed again after retirement.
  4. In tests, await runtime disposal before ending the test to avoid post-teardown calls.

Example fix

// before
await oldRuntime.initialize(); // may throw after retire()
// after
if (!oldRuntime.isRetired()) {
  await oldRuntime.initialize();
} else {
  runtime = createRuntime(config); // fresh instance
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (runtime.isRetired?.()) throw new Error("runtime already retired; create a new one");

Type guard

null

Try / catch

try {
  await runtime.initialize();
} catch (err) {
  if (/was retired/.test(String(err?.message))) {
    runtime = createRuntime(config); // recreate after shutdown
    await runtime.initialize();
  } else throw err;
}

Prevention

When it happens

Trigger: Calling initialize(), startDiscordGateway(), or other guarded methods after the runtime's retire()/shutdown path has set this.retired = true; a pending this.initialization promise resolving after retirement.

Common situations: Server shutdown or company teardown racing with an in-flight initialize; Discord gateway restart attempted after retirement; a request arriving during hot adapter reload/replacement; tests tearing down the runtime while a promise is still pending.

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/514f3a676f03ca8d. Report an issue: GitHub.