jackwener/OpenCLI · error · CliError

FETCH_ERROR

FETCH_ERROR

Error message

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

What it means

If the response is JSON, not an auth errcode, but resp.ok is false (any non-2xx other than the 401 auth path already handled), postWebApiWithCookies throws this CliError with code FETCH_ERROR and the HTTP status in the message. It means the WeRead API endpoint itself failed or is unavailable, independent of the caller's arguments.

Source

Thrown at clis/weread/ai-outline.js:49

        body: JSON.stringify(body),
    });

    if (resp.status === 401) {
        throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');
    }

    let data;
    try {
        data = await resp.json();
    } catch {
        throw new CliError('PARSE_ERROR', `Invalid JSON response for ${path}`, 'WeRead may have returned an HTML error page');
    }

    if (data?.errcode === -2010 || data?.errcode === -2012) {
        throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');
    }
    if (!resp.ok) {
        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
    }
    return data;
}

async function postWebApi(path, body) {
    const url = `${WEB_API}${path}`;
    const resp = await fetch(url, {
        method: 'POST',
        headers: {
            'User-Agent': WEREAD_UA,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
    });
    if (!resp.ok) {
        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
    }
    try {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry with exponential backoff — transient 5xx/502/503 usually resolve within minutes.
  2. Slow down request rate and add jitter if you saw HTTP 429; respect rate limits.
  3. Verify the endpoint path is still valid (HTTP 404 suggests WeRead changed its API surface; update the CLI).
  4. Try from a different network/IP if you get persistent 403 (WAF blocking), and check WeRead's service status.

Example fix

// before
const data = await postWebApiWithCookies(path, body); // dies on HTTP 502
// after
const data = await retry(() => postWebApiWithCookies(path, body), {
  retries: 3, backoff: (n) => 2 ** n * 500, retryOn: (e) => e.code === 'FETCH_ERROR'
});
Defensive patterns

Strategy: retry

Try / catch

const backoff = (n) => 2 ** n * 500 + Math.random() * 250;
let data;
for (let attempt = 0; attempt < 4; attempt++) {
  try { data = await postWebApiWithCookies(path, body); break; }
  catch (e) {
    if (e instanceof CliError && e.code === 'FETCH_ERROR' && attempt < 3) {
      await new Promise(r => setTimeout(r, backoff(attempt)));
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: chapterData hitting the web API and receiving HTTP 5xx (server error), 403 (blocked/WAF), 429 (rate limited), 404 (path changed), or 502/503 (gateway) — any non-ok status after the 401 and errcode checks.

Common situations: WeRead maintenance windows or temporary outages; aggressive scripted polling triggering rate limits; WAF blocking datacenter IPs; API path changes after a WeRead site update.

Related errors


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