ruvnet/ruflo · error · PluginError
DEPENDENCY_NOT_FOUND
DEPENDENCY_NOT_FOUND
Error message
Cannot unload plugin '${pluginName}': depended on by ${dependents.join(', ')} What it means
unloadPlugin() refuses to unload a plugin that other loaded plugins declare in their dependencies array; the error message lists the dependent plugin names, and the PluginError code is 'DEPENDENCY_NOT_FOUND' (a misleading label for 'has dependents'). Removing a dependency out from under a live plugin would leave it calling into freed code, so dependents must be unloaded first - i.e. reverse of the initialization order exposed by getInitializationOrder().
Source
Thrown at v3/@claude-flow/shared/src/plugin-loader.ts:195
}
/**
* Unload a single plugin
*/
async unloadPlugin(pluginName: string): Promise<void> {
const pluginInfo = this.registry.getPlugin(pluginName);
if (!pluginInfo) {
throw new PluginError(
`Plugin '${pluginName}' not found`,
pluginName,
'INVALID_PLUGIN'
);
}
// Check for dependents
const dependents = this.findDependents(pluginName);
if (dependents.length > 0) {
throw new PluginError(
`Cannot unload plugin '${pluginName}': depended on by ${dependents.join(', ')}`,
pluginName,
'DEPENDENCY_NOT_FOUND'
);
}
// Shutdown plugin
await this.shutdownPlugin(pluginInfo.plugin);
// Unregister plugin
this.registry.unregisterPlugin(pluginName);
// Remove from initialization order
const index = this.initializationOrder.indexOf(pluginName);
if (index !== -1) {
this.initializationOrder.splice(index, 1);
}
}View on GitHub (pinned to fa13ee4ad6)
Solutions
- Unload in reverse initialization order: [...loader.getInitializationOrder()].reverse()
- Or unload the dependents named in the error message first, then the target
- For 'remove the whole subtree' semantics, walk the dependents list transitively before the target
Example fix
// before
await loader.unloadPlugin('storage'); // 'auth' depends on it -> throws
// after
for (const name of [...loader.getInitializationOrder()].reverse()) {
await loader.unloadPlugin(name); // dependents come off before their dependencies
} Defensive patterns
Strategy: validation
Validate before calling
// unload in reverse init order so dependents always go before their dependencies
for (const name of [...loader.getInitializationOrder()].reverse()) {
await loader.unloadPlugin(name);
} Try / catch
try {
await loader.unloadPlugin(target);
} catch (e) {
if (e instanceof PluginError && e.code === 'DEPENDENCY_NOT_FOUND') {
const dependents = e.message.match(/by (.+)$/)?.[1]?.split(', ') ?? [];
for (const d of dependents) await loader.unloadPlugin(d);
await loader.unloadPlugin(target); // now safe
return;
}
throw e;
} Prevention
- Always tear down plugins in reverse getInitializationOrder()
- Model plugin dependencies explicitly and never hardcode teardown order
- Parse the dependents list from the message when handling targeted unload requests
When it happens
Trigger: Unloading 'storage' while 'auth' declares dependencies: ['storage']; teardown order hardcoded instead of reverse-init; a partial unload loop where one dependent failed to unload and the loop continues on to its dependency; dynamic unload triggered by an admin action.
Common situations: Layered plugin systems (core -> storage -> auth); tests unloading one plugin in isolation; runtime enable/disable features that ignore the dependency graph.
Related errors
- INVALID_PLUGIN
- Cannot enable '${flag}': dependency '${dep}' is not enabled
- DUPLICATE_PLUGIN
- Failed to register MCP tools: ${registration.failed.join(',
- ${result.stderr?.toString() || 'Installation failed'}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/9fd3ecc381feab76.
Report an issue: GitHub.