jackwener/OpenCLI · error · CliError
PARSE_ERROR
PARSE_ERROR
Error message
PARSE_ERROR: Invalid JSON response for ${path} What it means
Thrown by fetchWebApi when the response from a public WeRead web endpoint has HTTP success status but its body cannot be parsed as JSON (resp.json() rejects). The hint notes WeRead may have returned an HTML error page — meaning the request looked successful to HTTP semantics but the payload is not the expected JSON.
Source
Thrown at clis/weread/utils.js:120
* 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}`);
if (params) {
for (const [k, v] of Object.entries(params))
url.searchParams.set(k, v);
}
const urlStr = url.toString();
// Merge cookies from both domains; API-domain cookies take precedence on name collisionView on GitHub (pinned to 49907e53dc)
Solutions
- Capture resp.text() and inspect the first bytes — if it's HTML ("<!DOCTYPE" or a captcha page), the request is being challenged; add cookies or browser-like headers.
- Retry the request — a truncated transfer or transient proxy issue often resolves on retry.
- Bypass suspect proxies/VPN and test direct connectivity with curl to compare bodies.
- If a WAF challenge page is returned, route requests through a real browser session or a headless browser instead of raw fetch.
- Catch CliError with code 'PARSE_ERROR' and fall back to an alternative data source (HTML scraping path).
Example fix
// before
try { return await resp.json(); } catch { throw new CliError('PARSE_ERROR', ...); }
// after
const text = await resp.text();
try { return JSON.parse(text); }
catch { throw new CliError('PARSE_ERROR', `Invalid JSON for ${path}; body starts with: ${text.slice(0, 120)}`); } Defensive patterns
Strategy: retry
Try / catch
try {
const data = await callWereadPublicApi();
} catch (e) {
if (e.code === 'PARSE_ERROR') {
// HTML error/captcha page likely; back off, or reroute through a browser session
await sleep(5000);
return retryOnceOrFallbackToScraping();
}
throw e;
} Prevention
- Inspect resp.text() (first 200 chars) when JSON parsing fails to identify HTML challenges
- Route requests through a real browser session if WAF challenges recur
- Avoid VPN/proxy exit nodes likely flagged by Tencent's edge
- Retry transient truncations; verify with curl if failures persist
When it happens
Trigger: resp.json() throws on the /web/* response body: an HTML captcha/WAF challenge page served with 200, an empty body, truncated/garbled response from a proxy, or Content-Type mismatch where the body is HTML text.
Common situations: Captive portals or corporate MITM proxies injecting HTML; WeRead anti-bot serving an HTML challenge with status 200; a CDN error page returned with 200; network middleware corrupting the response mid-transfer.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Ctrip flight API returned invalid JSON
- toutiao hot-board returned malformed JSON: ${error?.message
- Failed to parse 12306 station_name.js: source string not fou
- ${label} returned a non-JSON response
- Chess.com callback returned malformed JSON for ${url}: ${err
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/aedcdba705b844f4.
Report an issue: GitHub.