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

  1. Read the interpolated gateway message — it usually names the failing tool or parameter — and correct the tool name/arguments accordingly.
  2. Verify `gatewayUrl` (or the default x.ruv.io endpoint) is current and the gateway version supports the method being called.
  3. Re-run after checking gateway health/status; transient gateway errors may clear on retry.
  4. If the error is auth-related, set RUFLO_X_ADMIN_TOKEN for admin-gated tools like `x_federation_publish`.
  5. 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

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


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 body

View on GitHub (pinned to 9c61c86f06)