ruvnet/ruflo · error · Error
FederationTransport: no address for peer ${to}
Error message
FederationTransport: no address for peer ${to} What it means
FederationTransport.send(to, ...) resolves the recipient through the caller-supplied addressOf callback passed in FederationTransportOptions. When addressOf(to) returns undefined the send fails before anything touches the wire — the transport refuses to send to peers it has no address mapping for. The mapping and the peerIds list are both injected, so this error always points at the host application's membership/addressing table, not at remote state.
Source
Thrown at v3/@claude-flow/swarm/src/consensus/federation-transport.ts:156
}
peers(): readonly string[] {
return this.peerIdsFn().filter(id => id !== this.nodeId);
}
private stamp(msg: Omit<ConsensusMessage, 'from'>): ConsensusMessage {
const base: Omit<ConsensusMessage, 'signature'> = {
...msg,
from: this.nodeId,
seq: this.keyPair ? ++this.seqCounter : msg.seq,
};
return this.keyPair ? { ...base, signature: signMessage(base, this.keyPair.privateKeyPem) } : base;
}
async send(to: string, msg: Omit<ConsensusMessage, 'from'>, timeoutMs?: number): Promise<ConsensusReply> {
if (this.closed) throw new Error('FederationTransport: closed');
const addr = this.addressOf(to);
if (!addr) throw new Error(`FederationTransport: no address for peer ${to}`);
const corr = randomBytes(8).toString('hex');
const stamped = this.stamp({ ...msg, to });
const t = timeoutMs ?? this.defaultTimeoutMs;
return new Promise<ConsensusReply>((resolve, reject) => {
const timer = setTimeout(() => { this.pending.delete(corr); reject(new Error(`FederationTransport: send to ${to} timed out (${t}ms)`)); }, t);
this.pending.set(corr, { resolve, reject, timer });
this.wire.send(addr, { type: 'consensus', payload: { corr, msg: stamped } as WireEnvelope, streamId: this.streamId })
.catch((e) => { clearTimeout(timer); this.pending.delete(corr); reject(e instanceof Error ? e : new Error(String(e))); });
});
}
async broadcast(msg: Omit<ConsensusMessage, 'from'>): Promise<void> {
if (this.closed) throw new Error('FederationTransport: closed');
const stamped = this.stamp(msg);
await Promise.allSettled(this.peers().map(async (to) => {
const addr = this.addressOf(to);
if (!addr) return;
await this.wire.send(addr, { type: 'consensus', payload: { msg: stamped } as WireEnvelope, streamId: this.streamId }).catch(() => {});View on GitHub (pinned to fa13ee4ad6)
Solutions
- Add the missing entry so addressOf(nodeId) returns the peer's WS/QUIC address.
- Pre-validate the target with the same addressOf function used to build the options before calling send.
- Re-sync the membership/address table (single source of truth for peerIds + addresses) and retry.
- Check for id normalization issues: exact string match, casing, whitespace.
Example fix
// before — the injected addressOf has no entry for 'node-7'
const transport = new FederationTransport(wire, {
nodeId: 'node-1',
addressOf: (id) => addresses[id], // addresses['node-7'] === undefined
peerIds: () => Object.keys(addresses),
});
await transport.send('node-7', msg); // throws
// after — keep the address table in sync with membership
addresses['node-7'] = 'wss://node-7.example/agentic-flow';
await transport.send('node-7', msg); Defensive patterns
Strategy: validation
Validate before calling
// you supply addressOf — pre-check it with the same function before send
const address = addressOf(nodeId);
if (!address) {
throw new Error(`no address for ${nodeId}; refresh the membership mapping first`);
}
await transport.send(nodeId, msg); Try / catch
try {
await transport.send(nodeId, msg);
} catch (e) {
if (e instanceof Error && e.message.startsWith('FederationTransport: no address for peer')) {
// refresh membership/address table, then retry once; otherwise drop the peer
} else {
throw e;
}
} Prevention
- Drive both addressOf and peerIds from a single membership source so they cannot diverge.
- Validate peer ids at the boundary where membership changes (join/leave) rather than at send time.
- Log unknown-peer failures — broadcast() silently skips addressless peers, which can mask partial membership.
When it happens
Trigger: Sending to a node id with no entry in the addressOf mapping; a typo or case mismatch in the peer id; the peer was removed from the address table during membership changes; federation membership updated on the consensus side but not in the address mapping (or vice versa).
Common situations: Cluster membership and transport addresses drifting apart; environment-specific node names (node-7 in staging, n7 in prod); copy-pasted ids from another cluster; a peer joined but its address was never published to this node's config.
Related errors
- FederationTransport: closed
- HTTP transport failed: ${firstError instanceof Error ? first
- Concurrent write detected on aggregate '${aggregateId}'. Res
- FederationBridge.decode: expected type 'claim-event', got '$
- FederationBridge.decode: payload missing or non-object
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/b6bd2fb474603661.
Report an issue: GitHub.