ruvnet/ruflo · error

fetchAgentCard: ${sourceUrl} returned HTTP ${res.status}

Error message

fetchAgentCard: ${sourceUrl} returned HTTP ${res.status}

What it means

The Agent Card HTTP request completed at the transport layer but the response status was outside 2xx (res.ok false). The body is never read; instead the resolved source URL and the HTTP status are reported. Note the request already carries an accept: application/json header and a default 10s abort timeout, so this error is purely about the server's status code.

Source

Thrown at v3/@claude-flow/plugin-agent-federation/src/a2a/consume.ts:63

  options: FetchAgentCardOptions = {},
): Promise<FetchAgentCardResult> {
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
  if (typeof fetchImpl !== 'function') {
    throw new Error('fetchAgentCard: no fetch implementation available');
  }
  const sourceUrl = resolveAgentCardUrl(baseOrCardUrl);
  const maxBytes = options.maxBytes ?? 256 * 1024;

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 10_000);
  let text: string;
  try {
    const res = await fetchImpl(sourceUrl, {
      signal: controller.signal,
      headers: { accept: 'application/json' },
    });
    if (!res.ok) {
      throw new Error(`fetchAgentCard: ${sourceUrl} returned HTTP ${res.status}`);
    }
    text = await res.text();
  } finally {
    clearTimeout(timer);
  }
  if (text.length > maxBytes) {
    throw new Error(`fetchAgentCard: card exceeds ${maxBytes} bytes`);
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(text);
  } catch {
    throw new Error(`fetchAgentCard: ${sourceUrl} did not return valid JSON`);
  }

  const validation = validateAgentCard(parsed);
  if (!validation.valid) {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Open the exact sourceUrl from the error message with curl or a browser and confirm the card is served
  2. Fix scheme/host/path — remember the resolver appends the well-known path when you pass a base URL
  3. For 401/403 supply the auth the peer's edge requires or get your egress IP whitelisted
  4. Retry transient 5xx with backoff; the call is a plain fetch you control, so wrap it in your own retry loop

Example fix

// before
const { card } = await fetchAgentCard(peerUrl);
// after
let card;
for (let i = 0; i < 3; i++) {
  try { card = (await fetchAgentCard(peerUrl)).card; break; }
  catch (e) {
    if (!String(e).includes('HTTP 5') || i === 2) throw e;
    await new Promise(r => setTimeout(r, 2 ** i * 250));
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight the URL cheaply before the card fetch
try {
  const u = new URL(base);
  await fetch(u.origin, { method: 'HEAD' });
} catch {
  throw new Error('peer origin unreachable: ' + base);
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await fetchAgentCard(url, opts);
  } catch (e) {
    const msg = String(e);
    const transient = msg.includes('HTTP 5') || msg.includes('HTTP 429');
    if (!transient || attempt === 2) throw e;
    await new Promise(r => setTimeout(r, 2 ** attempt * 250));
  }
}

Prevention

When it happens

Trigger: Wrong base URL where the well-known agent.json path is not served (404); the peer or an intermediary requires authentication (401/403); reverse proxy misroute or upstream down (502/503); the card endpoint moved without a redirect.

Common situations: Typo'd or stale peer hostname; peer deployed behind a gateway needing a path prefix; transient 5xx while the peer redeploys; firewall returning 403 for unknown egress IPs.

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


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