jackwener/OpenCLI · error · CommandExecutionError
eastmoney convertible returned invalid JSON: ${error?.messag
Error message
eastmoney convertible returned invalid JSON: ${error?.message ?? error} What it means
CommandExecutionError thrown (clis/eastmoney/convertible.js:156) when `resp.json()` rejects — i.e. the HTTP response was OK (2xx) but the body is not valid JSON. The original parse error message is embedded. Eastmoney endpoints normally return JSON, so this indicates the body was HTML or otherwise non-JSON despite a success status.
Source
Thrown at clis/eastmoney/convertible.js:156
const url = new URL('https://push2.eastmoney.com/api/qt/clist/get');
url.searchParams.set('pn', '1');
url.searchParams.set('pz', String(limit));
url.searchParams.set('po', sort.order === 'desc' ? '1' : '0');
url.searchParams.set('np', '1');
url.searchParams.set('fltt', '2');
url.searchParams.set('invt', '2');
url.searchParams.set('fid', sort.fid);
url.searchParams.set('fs', 'b:MK0354');
url.searchParams.set('fields', 'f12,f14,f2,f3,f6,f229,f230,f232,f234,f235,f236,f237,f238,f239,f243');
url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
if (!resp.ok) throw new CommandExecutionError(`eastmoney convertible failed: HTTP ${resp.status}`);
let data;
try {
data = await resp.json();
} catch (error) {
throw new CommandExecutionError(`eastmoney convertible returned invalid JSON: ${error?.message ?? error}`);
}
const diff = extractConvertibleDiff(data);
return mapConvertibleRows(diff, limit);
},
});
export const __test__ = { SORTS, extractConvertibleDiff, mapConvertibleRow, mapConvertibleRows, parseConvertibleLimit };
View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw response text to see whether it's HTML (block page) or truncated JSON.
- Retry — transient interception often clears on a second request.
- Check proxy/VPN/captive-portal interference and try a direct connection.
- Send a fuller browser User-Agent to avoid WAF interstitials.
- If persistent, inspect whether the endpoint moved and accept a content-type check before parsing.
Example fix
// before
const data = await resp.json();
// after
const text = await resp.text();
let data;
try {
data = JSON.parse(text);
} catch (e) {
throw new Error('non-JSON body: ' + text.slice(0, 200));
} Defensive patterns
Strategy: try-catch
Validate before calling
const ct = resp.headers.get('content-type') || '';
if (!ct.includes('json')) throw new Error(`expected JSON, got ${ct}; likely blocked page`);
const text = await resp.text(); Try / catch
try {
data = await resp.json();
} catch (error) {
const body = await resp.text().catch(() => '');
console.error('non-JSON body preview:', body.slice(0, 200));
if (/html/i.test(body)) console.error('blocked/interstitial page — change IP or User-Agent');
return retryWithBackoff(fetchConvertible);
} Prevention
- Check content-type before calling resp.json().
- Capture and log the raw text on parse failure for diagnosis.
- Avoid routes through captive portals or rewriting proxies when scraping.
- Retry once on parse failure — interception is often transient.
- Use a realistic browser User-Agent to avoid HTML interstitials.
When it happens
Trigger: The push2 endpoint returns HTTP 200 with an HTML anti-bot/verification page, a captive-portal or proxy interception page, truncated/garbled transfer, or a `charset` mismatch that breaks JSON.parse. Also fires if a middlebox rewrites the response body.
Common situations: WAF soft-blocks that still return 200; hotel/office captive portals intercepting HTTPS through a misconfigured proxy; eastmoney serving a region-block interstitial; network middleboxes stripping content-encoding and corrupting the body.
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
- Reuters search returned a non-JSON body${detail}
- 12306 queryByTrainNo returned non-JSON body
- 12306 ${endpoint} returned non-JSON body
- 12306 ${endpoint} returned an unexpected payload shape
- archive search returned malformed JSON: ${error?.message ||
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/b9539f70c90b5b66.
Report an issue: GitHub.