ruvnet/ruflo · error · Error
x.ruv.io
Error message
x.ruv.io: ${payload.error.message ?? 'rpc error'} What it means
Thrown by `gatewayRpc` when the x.ruv.io gateway's JSON-RPC response contains an `error` object. The gateway accepted the transport (an HTTP response arrived and parsed) but rejected the RPC call itself — e.g. unknown method, invalid params, or the remote tool raised an error. The gateway's error message is interpolated; 'rpc error' is the fallback when the error object has no message.
Solutions
- Read the interpolated gateway message — it usually names the failing tool or parameter — and correct the tool name/arguments accordingly.
- Verify `gatewayUrl` (or the default x.ruv.io endpoint) is current and the gateway version supports the method being called.
- Re-run after checking gateway health/status; transient gateway errors may clear on retry.
- If the error is auth-related, set RUFLO_X_ADMIN_TOKEN for admin-gated tools like `x_federation_publish`.
- Log the full JSON-RPC payload (result vs error) when debugging, since the thrown message can be the generic 'rpc error' fallback.
Example fix
// before: silently assuming the call succeeded shape-wise
const r = await gatewayTool('federation_publish', args);
// after: pass a valid gatewayUrl and catch the RPC error for diagnostics
try {
const r = await gatewayTool('federation_publish', { ...args, gatewayUrl: 'https://x.ruv.io' });
} catch (e) {
console.error('gateway rpc failed:', (e as Error).message);
} Defensive patterns
Strategy: try-catch
Validate before calling
const url = (args.gatewayUrl as string) ?? 'https://x.ruv.io';
try { new URL(url); } catch { throw new Error(`invalid gatewayUrl: ${url}`); }
if (!args.name || typeof args.name !== 'string') throw new Error('gateway tool name is required'); Type guard
function isRpcPayload(p: unknown): p is { result?: unknown; error?: { message?: string } } {
return typeof p === 'object' && p !== null && ('result' in p || 'error' in p);
} Try / catch
try {
const result = await gatewayRpc('tools/call', { name, arguments }, gatewayUrl);
} catch (e) {
if (e instanceof Error && e.message.startsWith('x.ruv.io:')) {
console.error('gateway rejected the RPC:', e.message);
// check gatewayUrl/version, auth, and arguments before retrying
} else throw e;
} Prevention
- Pin and periodically verify the gatewayUrl points at a compatible gateway version.
- Validate tool arguments against the tool's inputSchema before remote calls.
- Log full JSON-RPC error bodies when debugging, since the message can be the generic 'rpc error'.
- Set RUFLO_X_ADMIN_TOKEN for admin-gated methods before calling.
- Monitor gateway health/status endpoints and back off on degradation.
When it happens
Trigger: Any gateway-backed call (`gatewayTool`, `gatewayResource`) whose `tools/call` or `resources/read` JSON-RPC response carries `error`: calling a tool name the gateway does not expose, malformed arguments failing remote schema validation, gateway-internal exceptions, or admin-gated operations invoked without a valid token. Also produced when the SSE-style response has no `data:` line and the raw body parses to a JSON-RPC error.
Common situations: A stale or custom `gatewayUrl` pointing at an incompatible gateway version; the gateway deploying a renamed tool (e.g. `federation_publish` changed); passing `arguments` that fail the remote inputSchema; the gateway being degraded/overloaded and returning structured errors; network proxies returning JSON error bodies instead of results.
Related errors
- gateway tool error
- channel must be pub: or prv:<16 hex>
- claim rejected ( )
- Concurrent write detected on aggregate
- Consensus is disabled
AI-assisted analysis of ruvnet/ruflo@9c61c86f06 (2026-09-22).
Data as JSON: /api/errors/d776643f4f11cdf5.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts:30
// ADR-125 precedence: the tool arg `gatewayUrl` (fed by `ruflo federation --gateway`)
// takes precedence over the RUFLO_X_GATEWAY_URL env var, which precedes the default.
const GATEWAY = (override?: unknown): string =>
((typeof override === 'string' && override) || process.env.RUFLO_X_GATEWAY_URL || 'https://x.ruv.io').replace(/\/$/, '');
const gatewayArg = { gatewayUrl: { type: 'string', description: 'Gateway base URL; takes precedence over RUFLO_X_GATEWAY_URL (default https://x.ruv.io).' } } as const;
const TIMEOUT_MS = 25_000;
/** Minimal MCP-over-Streamable-HTTP client: POST JSON-RPC, parse the SSE `data:` frame. */
async function gatewayRpc(method: string, params: Record<string, unknown>, gatewayUrl?: unknown): Promise<unknown> {
const res = await fetch(`${GATEWAY(gatewayUrl)}/mcp`, {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params }),
signal: AbortSignal.timeout(TIMEOUT_MS),
});
const text = await res.text();
const line = text.split('\n').find((l) => l.startsWith('data:'));
const payload = JSON.parse(line ? line.slice(5) : text) as { result?: unknown; error?: { message?: string } };
if (payload.error) throw new Error(`x.ruv.io: ${payload.error.message ?? 'rpc error'}`);
return payload.result;
}
/**
* Relay-sourced gateway responses are not bare JSON. Since #3300 the gateway
* wraps anything published by other federation members in a provenance envelope
* (plugins/ruflo-x-gateway/src/untrusted.mjs): several lines of gateway-authored
* prose, then the JSON body between a matched
* `<<<UNTRUSTED_RELAY_DATA <uuid>>>>` / `<<<END_UNTRUSTED_RELAY_DATA <uuid>>>>`
* pair. `JSON.parse` on the whole string fails on the first prose word, which is
* the `Unexpected token 'T', "The block "...` seen from every federation read.
*
* Three things keep a publisher from closing the block early, and it is worth
* being precise about which one is doing the work today:
* 1. The body is JSON.stringify'd, so it is a SINGLE line — a publisher's text
* cannot contain a raw newline, and the markers below are newline-anchored.
* This is what actually neutralises forged markers in relay content today.
* 2. The token backreference: a forged END carrying any other token does not
* terminate the region. This is the defence that survives (1) — if the bodyView on GitHub (pinned to 9c61c86f06)