ruvnet/ruflo · error
Invalid plugin: does not implement IPlugin interface
Error message
Invalid plugin: does not implement IPlugin interface
What it means
Thrown by the basic PluginRegistry.register() (the non-enhanced registry in plugins/src/registry/plugin-registry.ts) when validatePlugin(resolvedPlugin) rejects the object. Same contract as the enhanced registry: register() first awaits factory functions, then requires the resolved value to implement IPlugin (metadata with name/version, lifecycle methods); a partial object, wrong export, or bad factory result fails here before the duplicate-name and max-plugins checks.
Source
Thrown at v3/@claude-flow/plugins/src/registry/plugin-registry.ts:204
}
// =========================================================================
// Plugin Loading
// =========================================================================
/**
* Register a plugin.
*/
async register(
plugin: IPlugin | PluginFactory,
config?: Partial<PluginConfig>
): Promise<void> {
// Resolve factory if needed
const resolvedPlugin = typeof plugin === 'function' ? await plugin() : plugin;
// Validate plugin
if (!validatePlugin(resolvedPlugin)) {
throw new Error('Invalid plugin: does not implement IPlugin interface');
}
const name = resolvedPlugin.metadata.name;
// Check for duplicates
if (this.plugins.has(name)) {
throw new Error(`Plugin ${name} already registered`);
}
// Check max plugins
if (this.config.maxPlugins && this.plugins.size >= this.config.maxPlugins) {
throw new Error(`Maximum plugin limit (${this.config.maxPlugins}) reached`);
}
// Create config
const pluginConfig: PluginConfig = {
enabled: true,
priority: 50,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Ensure the resolved object fully implements IPlugin: metadata.name, metadata.version, init(), shutdown(), state
- If using a factory, return the complete plugin object; if using an instance, pass the instance directly
- Check the import shape (module.default vs named) and register the instance your bundler actually produces
- Match the lifecycle method names required by the current registry version
Example fix
// before
import * as MyPlugin from './my-plugin'; // namespace object, not the plugin
await registry.register(MyPlugin); // -> throws Invalid plugin
// after
import { myPlugin } from './my-plugin'; // the IPlugin instance
await registry.register(myPlugin); Defensive patterns
Strategy: type-guard
Type guard
function isIPlugin(candidate: unknown): candidate is IPlugin {
if (typeof candidate !== 'object' || candidate === null) return false;
const p = candidate as Record<string, unknown>;
const meta = p.metadata as Record<string, unknown> | undefined;
return (
!!meta &&
typeof meta.name === 'string' &&
typeof meta.version === 'string' &&
typeof p.init === 'function' &&
typeof p.shutdown === 'function'
);
} Try / catch
try {
await registry.register(candidate);
} catch (err) {
if (err instanceof Error && err.message.includes('does not implement IPlugin')) {
logger.error('rejected plugin shape; keys:', Object.keys(candidate ?? {}));
return; // skip bad plugin, keep booting
}
throw err;
} Prevention
- Type all plugin exports as IPlugin so the compiler enforces the contract
- For dynamic plugin loading, run isIPlugin() on the resolved export and skip-with-log on failure
- Re-check plugin bundles after SDK upgrades - lifecycle method renames surface here first
When it happens
Trigger: Passing a plain object without metadata or lifecycle methods; a plugin factory resolving to a partial or undefined value; importing a module namespace instead of the plugin instance (ESM/CJS interop); a plugin written for a different registry interface version (e.g. initialize() instead of init()).
Common situations: Custom plugin authoring that misses shutdown() or state; default/named import mistakes; bundlers transforming plugin modules so the default export is wrapped; plugin SDK upgrades renaming lifecycle methods while old plugins are loaded by the basic registry.
Related errors
- Invalid plugin: does not implement IPlugin interface
- Invalid plugin replacement
- Plugin ${name} already registered
- Maximum plugin limit (${this.config.maxPlugins}) reached
- Plugin ${name} requires core version >= ${resolvedPlugin.met
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/accf5d4b27dd75ef.
Report an issue: GitHub.