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

  1. Add the missing entry so addressOf(nodeId) returns the peer's WS/QUIC address.
  2. Pre-validate the target with the same addressOf function used to build the options before calling send.
  3. Re-sync the membership/address table (single source of truth for peerIds + addresses) and retry.
  4. 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

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


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