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

  1. Ensure the resolved object fully implements IPlugin: metadata.name, metadata.version, init(), shutdown(), state
  2. If using a factory, return the complete plugin object; if using an instance, pass the instance directly
  3. Check the import shape (module.default vs named) and register the instance your bundler actually produces
  4. 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

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


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