jackwener/OpenCLI · error · CliError

FETCH_ERROR

FETCH_ERROR

Error message

FETCH_ERROR: HTTP ${resp.status} for ${path}

What it means

Thrown by fetchWebApi in clis/weread/utils.js when a public WeRead web API call (https://weread.qq.com/web/*) returns a non-2xx HTTP status. It is a CliError with code FETCH_ERROR and a hint that the API may be temporarily unavailable. This covers all public endpoints used by browser-free commands like search and ranking.

Source

Thrown at clis/weread/utils.js:114

      poll();
    }))
  `;
}
/**
 * Fetch a public WeRead web endpoint (Node.js direct fetch).
 * Used by search and ranking commands (browser: false).
 */
export async function fetchWebApi(path, params) {
    const url = new URL(`${WEB_API}${path}`);
    if (params) {
        for (const [k, v] of Object.entries(params))
            url.searchParams.set(k, v);
    }
    const resp = await fetch(url.toString(), {
        headers: { 'User-Agent': WEREAD_UA },
    });
    if (!resp.ok) {
        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
    }
    try {
        return await resp.json();
    }
    catch {
        throw new CliError('PARSE_ERROR', `Invalid JSON response for ${path}`, 'WeRead may have returned an HTML error page');
    }
}
/**
 * Fetch a private WeRead API endpoint with cookies extracted from the browser.
 * The HTTP request itself runs in Node.js to avoid page-context CORS failures.
 *
 * Cookies are collected from both the API subdomain (i.weread.qq.com) and the
 * main domain (weread.qq.com). WeRead may set auth cookies as host-only on
 * weread.qq.com, which won't match i.weread.qq.com in a URL-based lookup.
 */
export async function fetchPrivateApi(page, path, params) {
    const url = new URL(`${API}${path}`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry with exponential backoff — transient 5xx/429 is the most common cause.
  2. Check the status: 403/429 → slow request rate, add browser-like headers, or supply cookies from a logged-in session; 404 → update the endpoint path after a WeRead API change; 5xx → wait for server recovery.
  3. Verify the endpoint manually: curl -A '<UA>' 'https://weread.qq.com/web/search/global?keyword=x' and inspect the body.
  4. Catch CliError with code 'FETCH_ERROR' in scripts and fall back to the HTML scraping path (loadSearchHtmlEntries).
  5. Check weread.qq.com status in a browser to rule out a full outage.

Example fix

// before
const data = await fetchWebApi('/search/global', { keyword: query });
// after
let data;
try {
  data = await fetchWebApi('/search/global', { keyword: query });
} catch (e) {
  if (e.code === 'FETCH_ERROR' && /HTTP (429|5\d\d)/.test(e.message)) {
    await new Promise(r => setTimeout(r, 2000));
    data = await fetchWebApi('/search/global', { keyword: query });
  } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Health probe of the public API before batch usage
const r = await fetch('https://weread.qq.com/web/search/global?keyword=ping', { headers: { 'User-Agent': WEREAD_UA } });
if (!r.ok) console.warn(`weread web API unhealthy (HTTP ${r.status})`);

Try / catch

try {
  const data = await callWereadCommand();
} catch (e) {
  if (e.code === 'FETCH_ERROR' && /HTTP \d{3}/.test(e.message)) {
    const status = Number(/HTTP (\d{3})/.exec(e.message)[1]);
    if (status === 429 || status >= 500) return withBackoffRetry(callWereadCommand);
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: Any fetchWebApi call (e.g. /search/global with {keyword}) where resp.ok is false: 403/429 WAF or rate-limit responses, 404 after WeRead route changes, 5xx server errors, 30x followed to an error page.

Common situations: Rate limiting after scripted bulk queries; WeRead deploying new API paths; edge/CDN errors (502/504 from the qq.com CDN); anti-bot challenges returning 403 to non-browser fetches.

Related errors


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