ruvnet/ruflo · error · PluginError
INVALID_PLUGIN
INVALID_PLUGIN
Error message
Plugin '${pluginName}' not found What it means
PluginLoader.unloadPlugin(pluginName) looks the name up in the plugin registry and throws PluginError with code 'INVALID_PLUGIN' when nothing is registered under that exact name. Note the code is misleading (the condition is 'not found', not 'invalid') - branch on the message or pluginName, not just the code. Names are case-sensitive; loaded names are visible via loader.getInitializationOrder().
Source
Thrown at v3/@claude-flow/shared/src/plugin-loader.ts:185
} finally {
results.totalDuration = Date.now() - startTime;
}
// Start health checks if enabled
if (this.config.enableHealthChecks) {
this.startHealthChecks();
}
return results;
}
/**
* 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);View on GitHub (pinned to fa13ee4ad6)
Solutions
- Use the exact registered name - list them first via loader.getInitializationOrder()
- Guard with a presence check before unloading
- In unload-then-reload flows, ensure the original load completed before unloading
Example fix
// before
await loader.unloadPlugin('auth'); // actual name is 'auth-plugin'
// after
const loaded = loader.getInitializationOrder();
if (loaded.includes('auth-plugin')) {
await loader.unloadPlugin('auth-plugin');
} Defensive patterns
Strategy: validation
Validate before calling
const loadedNames = loader.getInitializationOrder();
if (!loadedNames.includes(pluginName)) {
throw new Error(`plugin '${pluginName}' is not loaded; loaded: ${loadedNames.join(', ')}`);
}
await loader.unloadPlugin(pluginName); Try / catch
try {
await loader.unloadPlugin(name);
} catch (e) {
if (e instanceof PluginError && e.code === 'INVALID_PLUGIN' && /not found/.test(e.message)) {
return; // already gone - idempotent unload
}
throw e;
} Prevention
- Source unload names from loader.getInitializationOrder(), never from stale config
- Names are case-sensitive: normalize or copy exactly
- Remember the code on this error is 'INVALID_PLUGIN' even though the cause is not-found
When it happens
Trigger: unloadPlugin('auth') when the plugin registered as 'AuthPlugin'; unloading before loadPlugins resolved; a typo in a config-driven unload list; calling unload twice where the first already removed it.
Common situations: Config lists using stale names after a plugin rename; case mismatches; hot-unload flows racing the initial load; teardown code written against docs instead of the actual registered names.
Related errors
- DEPENDENCY_NOT_FOUND
- DUPLICATE_PLUGIN
- hexToBytes: odd-length hex string
- SSRF guard: only HTTPS URLs are permitted, got ${parsed.prot
- SSRF guard: private/loopback host rejected — ${host}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f7d2bb749aad3e48.
Report an issue: GitHub.