ruvnet/ruflo · error
peer returned
Error message
peer ${peer.nodeId} returned ${res.status} What it means
syncRoomFromPeer fetches envelopes from a federation peer over HTTP; if the response status is not OK (res.ok false), it throws an error embedding the peer's node ID and HTTP status code. This surfaces non-2xx peer responses (404, 500, 429, etc.) as a uniform sync failure.
Solutions
- Check the HTTP status in the message and the peer's URL/port configuration to make sure it points at the running peer's sync endpoint.
- Retry the sync after a short backoff for transient statuses (502/503/504); respect 429 Retry-After.
- Verify the room exists on the peer (create/join it there) if the status is 404.
- Check the peer's logs around the request time to diagnose 5xx server-side failures.
Example fix
// before
await syncRoomFromPeer(peer, roomId); // throws on 500
// after
try {
await syncRoomFromPeer(peer, roomId);
} catch (e) {
if (/returned (502|503|504|429)/.test(e.message)) {
await delay(backoff);
await syncRoomFromPeer(peer, roomId);
} else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
// preflight
const res = await fetch(peerSyncUrl, { method: 'HEAD' });
if (!res.ok) console.warn(`peer unreachable, status ${res.status}`); Type guard
function isOkStatus(status: number): boolean { return status >= 200 && status < 300; } Try / catch
try {
await syncRoomFromPeer(peer, roomId);
} catch (e) {
const m = /returned (\d{3})/.exec(e.message);
const status = m ? Number(m[1]) : 0;
if ([429, 502, 503, 504].includes(status)) {
await new Promise(r => setTimeout(r, backoff));
return syncRoomFromPeer(peer, roomId);
}
throw e;
} Prevention
- Health-check peers before syncing
- Use exponential backoff with jitter for retries
- Alert on persistent 5xx from a peer
- Keep peer URLs in config and verify after peer restarts
When it happens
Trigger: Calling syncRoomFromPeer (directly or via 'r'/'second'/agentbbsTools) when the peer's sync endpoint returns a non-2xx status — peer not running, wrong URL/port, room missing on peer, or peer erroring under load.
Common situations: Peer node restarted or unreachable at the configured URL; room not yet created on the seconding peer (404); peer rate-limiting sync requests (429); transient 502/503 from a reverse proxy.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to fetch /models
- Failed to fetch manifest from
- Failed to get analytics
- Failed to get bulk ratings
- Failed to get ratings
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15).
Data as JSON: /api/errors/6739b112f5faa6ea.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts:392
}
/** Pull one room from one peer and merge what verifies. */
export async function syncRoomFromPeer(
basePath: string,
peer: FederationPeer,
roomId: string,
fetchImpl: typeof fetch = fetch,
): Promise<MergeResult & { peerNodeId: string; roomId: string }> {
validateRoomId(roomId);
const since = peer.lastSeq?.[roomId] ?? 0;
const url = `${peer.url}/agentbbs/v1/rooms/${encodeURIComponent(roomId)}/envelopes?since=${since}`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), SYNC_TIMEOUT_MS);
let body: any;
try {
const res = await fetchImpl(url, { signal: controller.signal, headers: { accept: 'application/json' } });
if (!res.ok) throw new Error(`peer ${peer.nodeId} returned ${res.status}`);
const text = await res.text();
if (text.length > MAX_SYNC_RESPONSE_BYTES) throw new Error(`peer ${peer.nodeId} response exceeds size cap`);
body = JSON.parse(text);
} finally {
clearTimeout(timer);
}
const envelopes: SignedEnvelope[] = Array.isArray(body?.envelopes) ? body.envelopes : [];
const merged = await mergeEnvelopes(basePath, roomId, envelopes, peer.publicKey);
const peers = readPeers(basePath);
const rec = peers.find(p => p.nodeId === peer.nodeId);
if (rec) {
rec.lastSyncedAt = new Date().toISOString();
rec.lastSeq = rec.lastSeq ?? {};
const maxSeq = envelopes.reduce((m, e) => Math.max(m, Number(e.seq) || 0), since);
rec.lastSeq[roomId] = maxSeq;
writePeers(basePath, peers);View on GitHub (pinned to 2602b642d9)