ruvnet/ruflo · error · Error

Plugin ${this.metadata.name} already initialized

Error message

Plugin ${this.metadata.name} already initialized

What it means

initialize() only accepts plugins in the 'uninitialized' state; any second call throws 'already initialized'. The state flips to 'initializing' immediately upon entry, before validation runs, so even retrying a failed initialize() on the same instance is rejected. This is a strict one-shot lifecycle machine, not an idempotent init.

Source

Thrown at v3/@claude-flow/plugins/src/core/base-plugin.ts:146

  protected get services(): ServiceContainer {
    return this.context.services;
  }

  protected get settings(): Record<string, unknown> {
    return this.config.settings;
  }

  // =========================================================================
  // Lifecycle Implementation
  // =========================================================================

  /**
   * Initialize the plugin.
   * Subclasses should override onInitialize() instead of this method.
   */
  async initialize(context: PluginContext): Promise<void> {
    if (this._state !== 'uninitialized') {
      throw new Error(`Plugin ${this.metadata.name} already initialized`);
    }

    this.setState('initializing');
    this._context = context;
    this._initTime = new Date();

    try {
      // Validate dependencies
      await this.validateDependencies();

      // Validate configuration
      await this.validateConfig();

      // Call subclass initialization
      await this.onInitialize();

      this.setState('initialized');
      this.eventBus.emit(PLUGIN_EVENTS.INITIALIZED, { plugin: this.metadata.name });

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Check the plugin's state before initializing and skip when it is not 'uninitialized'
  2. Create a fresh plugin instance for each initialization instead of reusing one (rebuild the plugin factory result)
  3. Wrap init in a once() memoization at the call site so repeated invocations share the first promise

Example fix

// before
await plugin.initialize(ctx); // ...later, somewhere else:
await plugin.initialize(ctx); // Error: already initialized

// after
const initPlugin = once(() => plugin.initialize(ctx));
await initPlugin(); // first call wins; later calls are no-ops
Defensive patterns

Strategy: validation

Validate before calling

import once from 'lodash/once'; // or a hand-rolled memo
const initPlugin = once(() => plugin.initialize(context));
await initPlugin(); // safe to call from every boot path

// explicit state check when once() is not available:
if (plugin.state === 'uninitialized') {
  await plugin.initialize(context);
}

Type guard

type Initializable = { state?: string };
function canInitialize(p: Initializable): boolean {
  return p.state === 'uninitialized';
}

Prevention

When it happens

Trigger: Calling plugin.initialize() or registry.initialize() twice on the same plugin instance; retry wrappers that re-run initialize after a transient failure; dev-mode double-mounting (React StrictMode effects, HMR) that executes setup code twice; registering one shared plugin object with two registries.

Common situations: Fire-and-forget init plus an explicit init elsewhere in boot code; retry-on-error logic around initialization; module singletons surviving hot reloads while init code re-runs.

Related errors


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