ruvnet/ruflo · error

Node ${agentId} already exists in topology

Error message

Node ${agentId} already exists in topology

What it means

TopologyManager.addNode() indexes nodes by agentId in nodeIndex; adding an agentId that is already present throws before any connections, edges, or partitions are touched. The guard keeps the graph consistent because a duplicate id would corrupt adjacency lists. No partial state is written when it throws.

Source

Thrown at v3/@claude-flow/swarm/src/topology-manager.ts:69

      this.config = { ...this.config, ...config };
      this.state.type = this.config.type;
    }

    this.emit('initialized', { type: this.config.type });
  }

  getState(): TopologyState {
    return {
      ...this.state,
      nodes: [...this.state.nodes],
      edges: [...this.state.edges],
      partitions: [...this.state.partitions],
    };
  }

  async addNode(agentId: string, role: TopologyNode['role']): Promise<TopologyNode> {
    if (this.nodeIndex.has(agentId)) {
      throw new Error(`Node ${agentId} already exists in topology`);
    }

    if (this.nodeIndex.size >= this.config.maxAgents) {
      throw new Error(`Maximum agents (${this.config.maxAgents}) reached`);
    }

    // Create node with connections based on topology type
    const connections = this.calculateInitialConnections(agentId, role);

    const node: TopologyNode = {
      id: `node_${agentId}`,
      agentId,
      role: this.determineRole(role),
      status: 'syncing',
      connections,
      metadata: {
        joinedAt: new Date().toISOString(),
        version: '1.0.0',

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Call removeNode(agentId) first (and let auto-rebalance settle) before re-adding a rejoining agent
  2. Make registration idempotent: check the manager's current nodes for the agentId before adding
  3. Scope one TopologyManager per test with proper teardown so ids never leak between cases

Example fix

// before
await topology.addNode(agentId, 'worker'); // throws on rejoin: already exists

// after
const exists = topology.getState().nodes.some(n => n.agentId === agentId);
if (exists) {
  await topology.removeNode(agentId);
}
await topology.addNode(agentId, 'worker');
Defensive patterns

Strategy: validation

Validate before calling

function nodeExists(topology: TopologyManager, agentId: string): boolean {
  return topology.getState().nodes.some(n => n.agentId === agentId);
}

if (nodeExists(topology, agentId)) {
  await topology.removeNode(agentId); // rejoin: replace the old node
}
await topology.addNode(agentId, 'worker');

Type guard

function isKnownAgentId(topology: TopologyManager, agentId: string): boolean {
  return topology.getState().nodes.some(n => n.agentId === agentId);
}

Try / catch

try {
  await topology.addNode(agentId, 'worker');
} catch (err) {
  if (err instanceof Error && err.message.endsWith('already exists in topology')) {
    await topology.removeNode(agentId);
    return topology.addNode(agentId, 'worker');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling addNode('worker-1', 'worker') twice; a rejoin handler that calls addNode on every reconnect without a prior removeNode; retry loops re-running registration after a timeout where the first add actually succeeded.

Common situations: Agent crash-restart logic that treats connect as add; at-least-once delivery of registration messages; test suites that reuse a shared TopologyManager across cases without teardown.

Related errors


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