musistudio/claude-code-router · warning

[plugin] Stop hook failed: ${formatError(error)}

Error message

[plugin] Stop hook failed: ${formatError(error)}

What it means

Warned by GatewayPluginService.stop when an individual plugin stop hook throws while being stopped (reason from stopReasonForPlugin: e.g. 'disabled' or config change). Iteration continues over remaining hooks and resource owners, so one plugin's failure to clean up does not block overall shutdown.

Source

Thrown at packages/core/src/plugins/service.ts:298

      try {
        await this.loadConfiguredPlugin(pluginConfig);
      } catch (error) {
        await this.rollbackConfiguredPluginLoad(pluginConfig.id, snapshot);
        console.warn(`[plugin:${pluginConfig.id}] Disabled after startup failure: ${formatError(error)}`);
      }
    }
  }

  async stop(options: { nextConfig?: AppConfig } = {}): Promise<void> {
    const stopHooks = [...this.stopHooks].reverse();
    const nextEnabledPluginIds = options.nextConfig ? enabledPluginIds(options.nextConfig) : undefined;
    this.stopHooks = [];

    for (const stopHook of stopHooks) {
      try {
        await stopHook.stop({ reason: stopReasonForPlugin(stopHook.pluginId, nextEnabledPluginIds) });
      } catch (error) {
        console.warn(`[plugin] Stop hook failed: ${formatError(error)}`);
      }
    }

    const resourceOwnerIds = [...this.resourceOwnerIds].reverse();
    this.resourceOwnerIds.clear();
    for (const ownerId of resourceOwnerIds) {
      await backendService.stopOwner(ownerId);
    }

    this.config = undefined;
    this.apps = [];
    this.coreGatewayConfig = {};
    this.coreProviderPlugins = [];
    this.gatewayRequestTransforms = [];
    this.gatewayRoutes = [];
    this.proxyRoutes = [];
    this.providerAccountConnectors.clear();
    this.virtualModelProfiles = [];

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Identify the plugin from the error and update/patch its stop hook.
  2. Report the throw to the plugin author — stop hooks must be defensive and never throw.
  3. If it blocks practical operation (e.g. lingering handles), restart the process after ensuring critical state is flushed.

Example fix

// plugin stop hook best practice
// before
stop: async () => { await this.server.close(); } // throws if server never started
// after
stop: async () => { if (this.server) await this.server.close().catch(() => {}); }
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling stop or start (which restarts), or loadConfiguredPlugin/rollbackConfiguredPluginLoad, where a plugin's registered stop callback rejects — unclosed server, hung async cleanup, teardown bug.

Common situations: A third-party plugin whose stop awaits something that never resolves or throws; shutdown racing in-flight requests handled by the plugin; teardown code assuming resources that were never created.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/053145d67e635153. Report an issue: GitHub.