jackwener/OpenCLI · error · CommandExecutionError
${label} returned malformed JSON: ${err?.message ?? err}
Error message
${label} returned malformed JSON: ${err?.message ?? err} What it means
juejinFetch parses the response with `resp.json()` and wraps any parse failure as a CommandExecutionError. This means the HTTP layer succeeded but the body was not valid JSON (truncated HTML, a WAF/challenge page, gzip corruption, or an empty 2xx body). The underlying SyntaxError message is appended for diagnosis.
Source
Thrown at clis/juejin/utils.js:117
throw new CommandExecutionError(
`${label} request failed: ${err?.message ?? err}`,
'Check that api.juejin.cn is reachable from this network.',
);
}
if (resp.status === 429) {
throw new CommandExecutionError(
`${label} returned HTTP 429 (rate limited)`,
'Juejin throttles bursty traffic; wait a few seconds and retry.',
);
}
if (!resp.ok) {
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
}
let payload;
try {
payload = await resp.json();
} catch (err) {
throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
}
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {
throw new CommandExecutionError(`${label} returned a malformed API envelope`);
}
if (payload.err_no !== 0) {
throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);
}
return payload;
}
export function readDataArray(payload, label) {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {
throw new CommandExecutionError(`${label} returned a malformed payload`);
}
if (!Array.isArray(payload.data)) {
throw new CommandExecutionError(`${label} returned a non-array data field`);
}
if (payload.data.length === 0) {View on GitHub (pinned to 49907e53dc)
Solutions
- Look at the appended parse-error message; if it mentions '<' or unexpected token, dump the raw body (`curl ... | head -c 500`) to see what was actually returned.
- Check for a proxy/VPN/WAF in the path returning HTML instead of the API response and bypass or fix it.
- Simply retry — truncated bodies from flaky networks are usually transient.
- If a proxy strips/rewrites content, set NO_PROXY for api.juejin.cn.
- Verify the endpoint still returns JSON (API change) by calling it manually with curl.
Example fix
// before (raw body never inspected)
const payload = await juejinFetch('/recommend_api/feed/v1', body, 'juejin recommend');
// after (caller-side diagnosis when this error repeats)
const resp = await fetch('https://api.juejin.cn/recommend_api/feed/v1', init);
const text = await resp.text();
try { JSON.parse(text); } catch { console.error('Non-JSON body:', text.slice(0, 300)); } Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
function isMalformedJsonError(err) {
return err instanceof CommandExecutionError && /malformed JSON/.test(err.message);
} Try / catch
async function fetchJsonWithRetry(path, body, label, attempts = 3) {
for (let i = 0; ; i++) {
try { return await juejinFetch(path, body, label); }
catch (err) {
if (!isMalformedJsonError(err) || i >= attempts - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * (i + 1))); // truncated bodies are often transient
}
}
} Prevention
- Retry once or twice automatically — truncated/intercepted bodies are usually transient network flakes.
- When it persists, capture the raw body (resp.text()) before parsing to identify HTML interstitials or proxy pages.
- Bypass suspicious proxies/VPNs for api.juejin.cn (NO_PROXY) if HTML injection is suspected.
- Keep timeouts enabled so half-open connections fail fast instead of yielding truncated bodies.
When it happens
Trigger: A 2xx response from api.juejin.cn whose body `JSON.parse` rejects: an anti-bot HTML interstitial, a proxy's error page, a truncated/chunked-transfer response, or content-type confusion returning text/html instead of application/json.
Common situations: Captive portals / hotel Wi-Fi intercepting HTTPS (rare, but misconfigured proxies do it); middleboxes injecting HTML into responses; Juejin serving a CAPTCHA page with 200; network flakiness truncating the body mid-download.
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
- Sina Finance rolling news API returned invalid JSON: ${error
- Chess.com callback returned malformed JSON for ${url}: ${err
- Chess.com API returned malformed JSON for ${url}: ${error?.m
- coingecko returned malformed JSON: ${error?.message || error
- ${label} returned malformed JSON: ${err?.message ?? err}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/51e1aa70c12385ea.
Report an issue: GitHub.