jackwener/OpenCLI · error · CliError

FETCH_ERROR

FETCH_ERROR

Error message

HTTP ${resp.status} ${resp.statusText} from ${finalUrl}

What it means

fetchSingle performs an HTTP request via fetch() and throws a CliError with code FETCH_ERROR when the response is not ok (non-2xx status). The message includes the HTTP status, statusText, and the fully rendered target URL so the developer can see exactly which request failed and why. It exists to surface remote HTTP failures as a typed, recognizable pipeline error instead of continuing with invalid data.

Source

Thrown at src/pipeline/steps/fetch.ts:34

  page: IPage | null, url: string, method: string,
  queryParams: Record<string, unknown>, headers: Record<string, unknown>,
  args: Record<string, unknown>, data: unknown,
): Promise<unknown> {
  const renderedParams: Record<string, string> = {};
  for (const [k, v] of Object.entries(queryParams)) renderedParams[k] = String(render(v, { args, data }));
  const renderedHeaders: Record<string, string> = {};
  for (const [k, v] of Object.entries(headers)) renderedHeaders[k] = String(render(v, { args, data }));

  let finalUrl = url;
  if (Object.keys(renderedParams).length > 0) {
    const qs = new URLSearchParams(renderedParams).toString();
    finalUrl = `${finalUrl}${finalUrl.includes('?') ? '&' : '?'}${qs}`;
  }

  if (page === null) {
    const resp = await fetch(finalUrl, { method: method.toUpperCase(), headers: renderedHeaders });
    if (!resp.ok) {
      throw new CliError('FETCH_ERROR', `HTTP ${resp.status} ${resp.statusText} from ${finalUrl}`);
    }
    return resp.json();
  }

  return page.fetchJson(finalUrl, { method: method.toUpperCase(), headers: renderedHeaders });
}

/**
 * Batch fetch: send all URLs into the browser as a single evaluate() call.
 * This eliminates N-1 cross-process IPC round trips, performing all fetches
 * inside the V8 engine and returning results as one JSON array.
 */
async function fetchBatchInBrowser(
  page: IPage, urls: string[], method: string,
  headers: Record<string, string>, concurrency: number,
): Promise<unknown[]> {
  const headersJs = JSON.stringify(headers);
  const urlsJs = JSON.stringify(urls);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the URL in the message with curl (with the same headers) to confirm the status and inspect the response body
  2. Fix the URL or path in the step config so it points at an existing endpoint
  3. Check/refresh the credentials in renderedHeaders for 401/403, and add rate-limit backoff for 429
  4. Add retry/backoff handling in the calling stepFetch if the upstream is intermittently failing

Example fix

// before
await step({ type: 'fetch', url: 'https://api.example.com/v1/user' }); // 404
// after
await step({ type: 'fetch', url: 'https://api.example.com/v1/users' });
Defensive patterns

Strategy: try-catch

Validate before calling

const url = new URL(renderedUrl);
if (!/^https?:$/.test(url.protocol)) throw new Error('bad url: ' + renderedUrl);

Type guard

function isFetchError(e: unknown): e is { code: 'FETCH_ERROR'; message: string } {
  return typeof e === 'object' && e !== null && 'code' in e && (e as any).code === 'FETCH_ERROR';
}

Try / catch

try {
  const data = await stepFetch(cfg);
} catch (e) {
  if (isFetchError(e)) {
    const status = parseInt(e.message.match(/HTTP (\d+)/)?.[1] ?? '0', 10);
    if (status === 429 || status >= 500) await retryWithBackoff();
    else console.error('Non-retryable fetch failure:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Any fetch step whose rendered URL/headers produce a non-ok response: 404 for a wrong path, 401/403 for missing or invalid renderedHeaders auth, 500 from the upstream server, 429 rate limiting. Thrown only on the page===null (non-paginated) branch; paginated requests go through page.fetchJson instead.

Common situations: Expired API tokens baked into headers, typos in the configured URL or query params after template rendering, the upstream API being down or rate-limiting bulk pipeline runs, an endpoint renamed after an API version change.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/f99c32a593f39d4a. Report an issue: GitHub.