ruvnet/ruflo · error · Error
PEER_UNKNOWN
PEER_UNKNOWN
Error message
PEER_UNKNOWN: ${targetNodeId} What it means
Raised when the federation layer attempts delivery of an envelope over a real transport but the target cannot be resolved: resolveAddress(targetNodeId) returns no address or discovery.getPeer() has no registry entry for the node. Note the contrast: with no transport configured the send is silently dropped as an in-process noop; PEER_UNKNOWN means routing was attempted and discovery does not know the peer.
Source
Thrown at v3/@claude-flow/plugin-agent-federation/src/plugin.ts:373
sendToNode: async (targetNodeId, envelope) => {
// ADR-104: real wire send via the loaded transport. If the
// transport failed to load OR the peer's address can't be
// resolved, log + return (the upstream RoutingService.send
// already wraps this in try/catch and returns a RoutingResult
// with the error to the caller).
if (!transport) {
context.logger.debug(
`Federation send (in-process noop): ${envelope.envelopeId} → ${targetNodeId}`,
);
return;
}
const address = resolveAddress(targetNodeId);
const peer = discovery.getPeer(targetNodeId);
if (!address || !peer) {
context.logger.warn(
`Federation send aborted: peer ${targetNodeId} not in discovery registry`,
);
throw new Error(`PEER_UNKNOWN: ${targetNodeId}`);
}
const signatureVersion = selectEnvelopeSignatureVersion(
signatureMode,
peer.capabilities.supportedProtocols,
envelope.messageType,
);
// Build the AgentMessage WITHOUT signature first, canonicalize
// the bytes, sign them, then attach the signature to metadata.
// The receiver runs the same canonicalization and verifies
// against this node's published public key.
const baseMessage: AgentMessage = {
id: envelope.envelopeId,
type: envelope.messageType,
payload: envelope as unknown,
metadata: {
sourceNodeId: envelope.sourceNodeId,
targetNodeId: envelope.targetNodeId,
sessionId: envelope.sessionId,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Verify the exact targetNodeId against the discovery registry (peer list/status tooling)
- Ensure the peer node is online and re-advertised itself (federation_init plus discovery), or add it to staticPeers
- If the peer just joined, retry after a short backoff to allow discovery propagation
Example fix
// before
await client.callTool('federation_send', { targetNodeId: 'node-b', envelope }); // PEER_UNKNOWN: node-b
// after
const peers = await client.callTool('federation_peers', {});
if (!peerIds(peers).includes('node-b')) {
await waitForDiscovery('node-b'); // or add node-b to staticPeers
}
await client.callTool('federation_send', { targetNodeId: 'node-b', envelope }); Defensive patterns
Strategy: try-catch
Validate before calling
const known = new Set<string>();
subscribeToDiscoveryUpdates(
(peer) => known.add(peer.nodeId),
(peer) => known.delete(peer.nodeId),
);
if (!known.has(targetNodeId)) {
throw new Error(`refusing send: ${targetNodeId} not in known peers; refresh discovery first`);
} Try / catch
try {
await federationSend(envelope, targetNodeId);
} catch (e) {
if (e instanceof Error && /^PEER_UNKNOWN: /.test(e.message)) {
const nodeId = e.message.slice('PEER_UNKNOWN: '.length);
await refreshDiscovery();
if (!isPeerKnown(nodeId)) deadLetter(envelope, nodeId); // do not blindly retry
} else throw e;
} Prevention
- Wait for a discovery event confirming a peer before the first send
- Keep staticPeers aligned with actual cluster membership
- Treat PEER_UNKNOWN as discovery lag or churn, not as a transient network blip to retry blindly
When it happens
Trigger: Sending to a nodeId that never appeared via discovery, a peer that disconnected and was evicted from the registry, a node missing from staticPeers, or a mistyped target ID — while a transport is configured.
Common situations: Race where the remote node just joined and discovery has not propagated yet; the peer process crashed and its entry expired; clusters split across environments or mismatched staticPeers lists.
Related errors
- No available workers for task
- HTTP transport failed: ${firstError instanceof Error ? first
- Failed to fetch ${baseURL}/models: ${response.status} ${resp
- Resolved IP for ${hostname} is internal (${address})
- Concurrent write detected on aggregate '${aggregateId}'. Res
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ef19287e4d241d45.
Report an issue: GitHub.