ruvnet/ruflo · error
nodeId is already pinned to a different publicKey; remove…
Error message
nodeId ${input.nodeId} is already pinned to a different publicKey; remove it first What it means
addPeer() pins each nodeId to the first publicKey it sees; changing that key later is exactly what a key-substitution (MITM) attack looks like, so the library refuses to overwrite it. To re-key a node you must explicitly remove the peer first and then add it with the new key. Updating url/label for a matching key is still allowed.
Solutions
- Intentionally rotate: removePeer(base, nodeId) then addPeer() with the new publicKey.
- If the key change is unexpected, treat it as a security signal — verify out-of-band with the peer owner before replacing the pinned key.
- Ensure nodeId uniqueness across your fleet so cloned nodes don't collide; regenerate identity on cloned machines.
- Keep the pinned key in your deployment config in sync with the node's actual key to avoid accidental mismatch.
Example fix
// before
addPeer(base, { nodeId, url, publicKey: newKey }); // throws: pinned to different key
// after
removePeer(base, nodeId); // explicit, audited key rotation
addPeer(base, { nodeId, url, publicKey: newKey }); Defensive patterns
Strategy: try-catch
Validate before calling
const peers = readPeers(basePath);
const existing = peers.find(p => p.nodeId === input.nodeId);
if (existing && existing.publicKey !== input.publicKey) throw new Error('key rotation required: remove peer first'); Try / catch
try {
addPeer(basePath, input);
} catch (e) {
if (/pinned to a different publicKey/.test(e.message)) {
// require explicit, audited rotation — do not auto-replace in production
removePeer(basePath, input.nodeId);
addPeer(basePath, input);
} else throw e;
} Prevention
- Treat any key mismatch as a security incident until verified out-of-band
- Run an explicit key-rotation runbook: remove then re-add with the new key
- Regenerate node identity keys on cloned machines to avoid nodeId collisions
- Keep pinned keys in version-controlled config synced with node keys
When it happens
Trigger: Calling addPeer() with a nodeId already present in the registry whose stored publicKey differs from input.publicKey — e.g. the node regenerated its identity key, or two different machines claim the same nodeId.
Common situations: Operator rotated a node's keypair and forgot to remove the old peer entry first; nodeId collision after cloning a node image; a peer presenting a forged key for a known nodeId (the attack this check exists to catch).
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- Concurrent write detected on aggregate
- peer response exceeds size cap
- AI budget file is a symlink (refusing)
- AI job registry is a symlink (refusing)
- AIDefence failed to load
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15).
Data as JSON: /api/errors/3ddc0c30ccc677fa.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts:276
}
export function addPeer(
basePath: string,
input: { nodeId: string; url: string; publicKey: string; label?: string },
): FederationPeer {
if (!NODE_ID_RE.test(input.nodeId ?? '')) throw new Error('nodeId must be 16 lowercase hex chars');
if (!HEX64_RE.test(input.publicKey ?? '')) throw new Error('publicKey must be 64 lowercase hex chars');
const url = validatePeerUrl(String(input.url));
const peers = readPeers(basePath);
if (peers.length >= MAX_PEERS) throw new Error(`peer registry is full (${MAX_PEERS})`);
const existing = peers.find(p => p.nodeId === input.nodeId);
if (existing) {
// Re-pinning a different key for a known nodeId is how a key-substitution
// attack would present. Require an explicit remove first.
if (existing.publicKey !== input.publicKey) {
throw new Error(`nodeId ${input.nodeId} is already pinned to a different publicKey; remove it first`);
}
existing.url = url;
if (input.label) existing.label = input.label;
writePeers(basePath, peers);
return existing;
}
const peer: FederationPeer = {
nodeId: input.nodeId,
url,
publicKey: input.publicKey,
label: input.label,
addedAt: new Date().toISOString(),
lastSeq: {},
};
peers.push(peer);
writePeers(basePath, peers);
return peer;View on GitHub (pinned to 2602b642d9)