{"record":{"id":"28748a05833b7398","repo":"ruvnet/ruflo","slug":"localtransport-unreachable-peer-to","errorCode":null,"errorMessage":"LocalTransport: unreachable peer ${to}","messagePattern":"LocalTransport: unreachable peer (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/swarm/src/consensus/transport.ts","lineNumber":259,"sourceCode":"      if (!pub || !verifyMessage(msg, pub)) {\n        throw new Error(`LocalTransport: signature verification failed for message from ${msg.from}`);\n      }\n      // Replay defense: seq must be strictly increasing per sender.\n      if (typeof msg.seq === 'number') {\n        const last = target.lastSeenSeq.get(msg.from) ?? 0;\n        if (msg.seq <= last) throw new Error(`LocalTransport: replayed/out-of-order seq from ${msg.from} (${msg.seq} <= ${last})`);\n        target.lastSeenSeq.set(msg.from, msg.seq);\n      }\n    }\n    if (!target.handler) return null;\n    const reply = await target.handler(msg);\n    return (reply ?? null) as ConsensusReply;\n  }\n\n  async send(to: string, msg: Omit<ConsensusMessage, 'from'>, timeoutMs?: number): Promise<ConsensusReply> {\n    if (this.closed) throw new Error('LocalTransport: closed');\n    const target = this.registry.get(to);\n    if (!target) throw new Error(`LocalTransport: unreachable peer ${to}`);\n    const stamped = this.stamp(msg);\n    const t = timeoutMs ?? this.defaultTimeoutMs;\n    return Promise.race([\n      this.deliver(target, stamped),\n      new Promise<ConsensusReply>((_, rej) => setTimeout(() => rej(new Error(`LocalTransport: send to ${to} timed out (${t}ms)`)), t)),\n    ]);\n  }\n\n  async broadcast(msg: Omit<ConsensusMessage, 'from'>): Promise<void> {\n    if (this.closed) throw new Error('LocalTransport: closed');\n    const stamped = this.stamp(msg);\n    await Promise.allSettled(\n      this.registry.peerIds(this.nodeId).map(id => {\n        const target = this.registry.get(id);\n        return target ? this.deliver(target, stamped).catch(() => {}) : Promise.resolve();\n      }),\n    );\n  }","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/swarm/src/consensus/transport.ts#L241-L277","documentation":"send() looks the recipient up in the LocalTransportRegistry and this error means no transport is registered under that nodeId — the peer never joined this registry, already left (close() unregisters), or the sender is looking in a different registry entirely.","triggerScenarios":"(1) Sending before the peer transport was constructed (registration happens in the constructor). (2) The peer called close(), which unregisters it. (3) Sender and target were given different registries — e.g., a test passes a fresh registry to some nodes but not others (default is the process-wide defaultLocalRegistry). (4) Typo/case mismatch in the nodeId string. (5) Duplicate nodeId: a later construction overwrote the map entry and then closed.","commonSituations":"Test isolation: creating nodes with `registry: new LocalTransportRegistry()` for some but not all participants Nodes joining/leaving dynamically while Raft leaders cache the last-known peer set Copying nodeIds across services where the registry is per-process (LocalTransport only reaches peers in the same process — cross-process needs FederationTransport) A node restarted with the same id after its old transport was closed and unregistered","solutions":["Construct the target LocalTransport (it self-registers in its constructor) before sending, or wait for a join event","Ensure every node shares ONE registry instance (pass the same registry option to all constructors)","Check spelling and casing of the nodeId; registry keys are exact strings","For a rejoining node, construct a new transport; do not send to an id whose transport closed","Remember LocalTransport is in-process only — use FederationTransport for real cross-node messaging"],"exampleFix":"// before\nconst registry = new LocalTransportRegistry();\nconst a = new LocalTransport('a', { registry });\nconst b = new LocalTransport('b'); // oops: joined defaultLocalRegistry\nawait a.send('b', msg); // unreachable peer b\n\n// after\nconst registry = new LocalTransportRegistry();\nconst a = new LocalTransport('a', { registry });\nconst b = new LocalTransport('b', { registry }); // same registry\nawait a.send('b', msg); // delivered","handlingStrategy":"validation","validationCode":"// Verify peer is registered before sending:\nconst reachable = transport.peers(); // excludes self, includes only registered, non-closed peers\nif (!reachable.includes(targetId)) {\n  await waitForPeerJoin(registry, targetId); // or fail fast with a clear message\n}","typeGuard":null,"tryCatchPattern":"try { await transport.send(to, msg); }\ncatch (e) {\n  if (e instanceof Error && e.message.startsWith('LocalTransport: unreachable peer')) {\n    // retry with backoff until peer joins, or proceed with remaining quorum\n    return retryAfterJoin(to, msg);\n  }\n  throw e;\n}","preventionTips":["Pass the SAME registry instance to every LocalTransport constructor","Construct (and await) all peer transports before starting consensus traffic","Match nodeId strings exactly — case-sensitive keys in the registry","Remember LocalTransport is in-process only; cross-process messaging needs FederationTransport"],"tags":["consensus","transport","peer-discovery","registry","typescript"],"backgroundTag":"peer-not-found","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}