ruvnet/ruflo · error

Plugin ${name} already registered

Error message

Plugin ${name} already registered

What it means

Thrown by EnhancedPluginRegistry.register() when a plugin with the same metadata.name is already present in the plugins map. The registry keys plugins strictly by name; the duplicate check runs right after interface validation and before max-plugins and version checks, so a duplicate is always rejected outright.

Source

Thrown at v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts:292

   */
  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)) {
        throw new Error(
          `Plugin ${name} requires core version >= ${resolvedPlugin.metadata.minCoreVersion}, ` +
          `but current version is ${this.config.coreVersion}`
        );
      }
    }
    if (resolvedPlugin.metadata.maxCoreVersion) {
      if (!satisfiesVersion(`<=${resolvedPlugin.metadata.maxCoreVersion}`, this.config.coreVersion)) {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Unregister the existing plugin first (await registry.unregister(name)) or reuse the already-registered instance instead of re-registering
  2. Check registration state before calling register (via the registry's list API) and make registration idempotent in setup code
  3. Give each distinct plugin a unique metadata.name

Example fix

// before
await registry.register(pluginA); // name: 'logger'
await registry.register(pluginB); // also name: 'logger' -> throws

// after
await registry.register(pluginA);
const registered = (await registry.list()).map(p => p.metadata.name);
if (!registered.includes('logger')) {
  await registry.register(pluginB);
}
// or: await registry.unregister('logger'); await registry.register(pluginB);
Defensive patterns

Strategy: validation

Validate before calling

const name = plugin.metadata.name;
const registered = (await registry.list()).map(p => p.metadata.name);
if (registered.includes(name)) {
  await registry.unregister(name); // or skip if already loaded
}
await registry.register(plugin);

Try / catch

try {
  await registry.register(plugin);
} catch (err) {
  if (err instanceof Error && /already registered$/.test(err.message)) {
    return; // idempotent bootstrap
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling register(plugin) twice for the same plugin; registering two different plugins that both declare metadata.name 'my-plugin'; re-running bootstrap code (e.g. tests with a shared registry) that registers plugins again; a hot-reload path that registers the new version without unregistering the old one.

Common situations: Test files each importing a global registry fixture; watch-mode/HMR re-executing setup; plugin bundles that self-register on import being imported twice; copy-pasted plugins keeping the same name field.

Related errors


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