musistudio/claude-code-router · warning

[plugin: ] Rollback stop hook failed

Error message

[plugin:${pluginId}] Rollback stop hook failed: ${formatError(error)}

What it means

During rollbackConfiguredPluginLoad, after a plugin's startup failed, each newly registered stop hook for that plugin is invoked with reason 'disabled' to undo partial registration; if one of those stop hooks throws, this warning is logged and rollback continues to the next hook and to backend resource cleanup.

Solutions

  1. Fix the underlying startup failure first (error 546 carries the root cause) — rollback warnings then disappear.
  2. Make the plugin's stop hooks idempotent and null-safe so they never throw during undo.
  3. Upgrade the plugin/core pair for lifecycle fixes.

Example fix

// before
stop: async () => { await this.db.close(); } // throws if start failed before db was created
// after
stop: async () => { await this.db?.close().catch(() => {}); }
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: A plugin fails mid-start after registering stop hooks, and one of those hooks itself throws during undo — commonly because the resource it closes was never fully created before the original failure.

Common situations: Half-initialized plugin state: start threw after partial setup; teardown assuming fields that are undefined; async cleanup racing the rollback.

Related errors


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

Appendix: source

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

  private async rollbackConfiguredPluginLoad(pluginId: string, snapshot: PluginServiceStateSnapshot): Promise<void> {
    const newStopHooks = this.stopHooks.slice(snapshot.stopHooks.length).reverse();
    this.apps = snapshot.apps;
    this.coreGatewayConfig = snapshot.coreGatewayConfig;
    this.coreProviderPlugins = snapshot.coreProviderPlugins;
    this.gatewayRequestTransforms = snapshot.gatewayRequestTransforms;
    this.gatewayRoutes = snapshot.gatewayRoutes;
    this.providerAccountConnectors = snapshot.providerAccountConnectors;
    this.proxyRoutes = snapshot.proxyRoutes;
    this.resourceOwnerIds = snapshot.resourceOwnerIds;
    this.stopHooks = snapshot.stopHooks;
    this.virtualModelProfiles = snapshot.virtualModelProfiles;

    for (const stopHook of newStopHooks) {
      try {
        await stopHook.stop({ reason: "disabled" });
      } catch (error) {
        console.warn(`[plugin:${pluginId}] Rollback stop hook failed: ${formatError(error)}`);
      }
    }

    try {
      await backendService.stopOwner(pluginId);
    } catch (error) {
      console.warn(`[plugin:${pluginId}] Rollback resource cleanup failed: ${formatError(error)}`);
    }
  }
}

export const pluginService = new GatewayPluginService();

function pluginPermissionAccess(pluginConfig: Pick<GatewayPluginConfig, "enabled" | "id" | "permissions">): PluginPermissionAccess {
  const permissions = pluginConfig.permissions
    ?? knownGatewayPluginDefaultPermissions(pluginConfig.id)
    ?? (pluginConfig.enabled === true ? undefined : [...GATEWAY_PLUGIN_PERMISSION_IDS]);
  return {

View on GitHub (pinned to 99f24806c6)