musistudio/claude-code-router · warning

[plugin: ] Rollback resource cleanup failed

Error message

[plugin:${pluginId}] Rollback resource cleanup failed: ${formatError(error)}

What it means

The final step of plugin-load rollback calls backendService.stopOwner(pluginId) to release the plugin's owned resources (SQLite stores, servers). If that call itself throws, this warning is logged and rollback ends; the process stays up but the plugin's backend resources may leak until restart.

Solutions

  1. Fix the plugin's original startup failure so rollback is never needed.
  2. Restart the gateway after such a warning to reclaim any leaked resources.
  3. Report/update the backend owner implementation whose stopOwner threw (the message includes the cause).
  4. Avoid config hot-reloads of the broken plugin until fixed.
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Rollback of a failed plugin load where stopOwner throws — e.g. an owned child process or store refuses to stop, or cleanup logic throws outside the per-store try/catch seen in stopOwner.

Common situations: Plugin that spawns processes/opens stores and fails startup; repeated config reloads accumulating leaked resources; a backend owner type with buggy teardown.

Related errors


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

Appendix: source

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

    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 {
    explicit: permissions !== undefined,
    permissions: new Set(permissions ?? []),
    pluginId: pluginConfig.id
  };
}

function pluginPermissionList(access: PluginPermissionAccess): GatewayPluginPermission[] {

View on GitHub (pinned to 99f24806c6)