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

  1. Check the resolved value implements the full IPlugin interface: metadata.name, metadata.version, init(), shutdown(), state - before registering
  2. If registering a factory, ensure it resolves (returns) the plugin object, not a class or partial object
  3. Verify the import: use the instance/value export, not the module namespace (check for .default in transpiled CJS)
  4. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/0ed884d9ba0d0f88. Report an issue: GitHub.