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
- Call removeNode(agentId) first (and let auto-rebalance settle) before re-adding a rejoining agent
- Make registration idempotent: check the manager's current nodes for the agentId before adding
- 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
- Treat agent join as an upsert in one helper (remove-then-add or check-then-add)
- Always await addNode so retry loops know whether the first attempt succeeded
- Log agentId on every add and remove to spot double registration in reconnect logs
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
- Maximum agents (${this.config.maxAgents}) reached
- Node ${agentId} not found
- No nodes available for leader election
- Maximum agents (${this.config.maxAgents}) reached
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ff2dc769bb74125a.
Report an issue: GitHub.