musistudio/claude-code-router · warning
[plugin:${pluginId}] Rollback stop hook failed: ${formatErro
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.
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)
Solutions
- Fix the underlying startup failure first (error 546 carries the root cause) — rollback warnings then disappear.
- Make the plugin's stop hooks idempotent and null-safe so they never throw during undo.
- 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
- Make stop hooks idempotent so rollback of partial starts succeeds
- Fix root startup failures promptly
- Initialize resources before registering their stop hooks
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
- [plugin] Stop hook failed: ${formatError(error)}
- [plugin:${pluginConfig.id}] Disabled after startup failure:
- [plugin:${pluginId}] Rollback resource cleanup failed: ${for
- [backend:${ownerId}] SQLite store close failed: ${formatErro
- [plugin-marketplace] Failed to fetch marketplace from ${url}
AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27).
Data as JSON: /api/errors/67e848da8f3de5dc.
Report an issue: GitHub.