{"record":{"id":"18a1932a6a227664","repo":"jackwener/OpenCLI","slug":"fetch-error-18a193","errorCode":"FETCH_ERROR","errorMessage":"HTTP ${resp.status} for ${path}","messagePattern":"HTTP (.+?) for (.+?)","errorType":"exception","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/weread/ai-outline.js","lineNumber":49,"sourceCode":"        body: JSON.stringify(body),\n    });\n\n    if (resp.status === 401) {\n        throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');\n    }\n\n    let data;\n    try {\n        data = await resp.json();\n    } catch {\n        throw new CliError('PARSE_ERROR', `Invalid JSON response for ${path}`, 'WeRead may have returned an HTML error page');\n    }\n\n    if (data?.errcode === -2010 || data?.errcode === -2012) {\n        throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');\n    }\n    if (!resp.ok) {\n        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');\n    }\n    return data;\n}\n\nasync function postWebApi(path, body) {\n    const url = `${WEB_API}${path}`;\n    const resp = await fetch(url, {\n        method: 'POST',\n        headers: {\n            'User-Agent': WEREAD_UA,\n            'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(body),\n    });\n    if (!resp.ok) {\n        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');\n    }\n    try {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/ai-outline.js#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"WeRead maintenance windows or temporary outages; aggressive scripted polling triggering rate limits; WAF blocking datacenter IPs; API path changes after a WeRead site update.","solutions":["Retry with exponential backoff — transient 5xx/502/503 usually resolve within minutes.","Slow down request rate and add jitter if you saw HTTP 429; respect rate limits.","Verify the endpoint path is still valid (HTTP 404 suggests WeRead changed its API surface; update the CLI).","Try from a different network/IP if you get persistent 403 (WAF blocking), and check WeRead's service status."],"exampleFix":"// before\nconst data = await postWebApiWithCookies(path, body); // dies on HTTP 502\n// after\nconst data = await retry(() => postWebApiWithCookies(path, body), {\n  retries: 3, backoff: (n) => 2 ** n * 500, retryOn: (e) => e.code === 'FETCH_ERROR'\n});","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const backoff = (n) => 2 ** n * 500 + Math.random() * 250;\nlet data;\nfor (let attempt = 0; attempt < 4; attempt++) {\n  try { data = await postWebApiWithCookies(path, body); break; }\n  catch (e) {\n    if (e instanceof CliError && e.code === 'FETCH_ERROR' && attempt < 3) {\n      await new Promise(r => setTimeout(r, backoff(attempt)));\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["Add exponential backoff with jitter around all WeRead API calls.","Only retry FETCH_ERROR — never retry AUTH_REQUIRED or PARSE_ERROR without action.","Inspect the status in the message (429 vs 5xx) and tune pacing accordingly.","Monitor for persistent 404s, which indicate the CLI's endpoint paths need updating after a WeRead site change."],"tags":["network","http-error","api-unavailable"],"backgroundTag":"http-5xx-server-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}