ruvnet/ruflo · error
Invalid plugin: does not implement IPlugin interface
Error message
Invalid plugin: does not implement IPlugin interface
What it means
Thrown by EnhancedPluginRegistry.register() when validatePlugin(resolvedPlugin) rejects the object. register() first resolves factory functions (plugin can be an async factory), then requires the result to satisfy the IPlugin shape - metadata (name, version), lifecycle methods (init/shutdown), state. An object or factory result missing those members fails validation before any name or version checks run.
Source
Thrown at v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts:284
}
// =========================================================================
// Plugin Loading
// =========================================================================
/**
* Register a plugin with version constraint validation.
*/
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;
const version = resolvedPlugin.metadata.version;
// 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`);
}
// Check core version compatibility
if (resolvedPlugin.metadata.minCoreVersion) {
if (!satisfiesVersion(`>=${resolvedPlugin.metadata.minCoreVersion}`, this.config.coreVersion)) {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Check the resolved value implements the full IPlugin interface: metadata.name, metadata.version, init(), shutdown(), state - before registering
- If registering a factory, ensure it resolves (returns) the plugin object, not a class or partial object
- Verify the import: use the instance/value export, not the module namespace (check for .default in transpiled CJS)
- Align with the current interface version of the plugin SDK - method names like init/shutdown must match exactly
Example fix
// before
registry.register({ metadata: { name: 'x', version: '1.0.0' } }); // no lifecycle -> throws
// after
registry.register({
metadata: { name: 'x', version: '1.0.0' },
state: 'registered',
async init(context) { this.state = 'initialized'; },
async shutdown() { this.state = 'shutdown'; },
} as unknown as IPlugin); 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(plugin);
} catch (err) {
if (err instanceof Error && err.message.includes('does not implement IPlugin')) {
throw new Error(
`plugin failed IPlugin shape check; keys: ${JSON.stringify(Object.keys(plugin ?? {}))}`
);
}
throw err;
} Prevention
- Type plugin factories as PluginFactory and plugin objects as IPlugin so the compiler catches missing members
- Unit-test each plugin against the IPlugin contract (metadata + init + shutdown) before shipping
- Watch ESM/CJS interop: register the resolved instance, not a module namespace or class reference
When it happens
Trigger: Passing a plain object literal that lacks metadata or lifecycle methods; passing a factory function that resolves to a partial plugin (e.g. returns { metadata } only); passing a class instead of an instance; a default-vs-named import mixup that hands over undefined or a module namespace object.
Common situations: ESM/CJS interop where the imported binding is a module namespace rather than the plugin instance; plugin package upgraded to a new API that renamed init to initialize; writing a custom plugin and forgetting shutdown(); factories that throw or return undefined under some conditions.
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/0ed884d9ba0d0f88.
Report an issue: GitHub.