{"record":{"id":"51e1aa70c12385ea","repo":"jackwener/OpenCLI","slug":"label-returned-malformed-json-err-message-51e1aa","errorCode":null,"errorMessage":"${label} returned malformed JSON: ${err?.message ?? err}","messagePattern":"(.+?) returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/utils.js","lineNumber":117,"sourceCode":"        throw new CommandExecutionError(\n            `${label} request failed: ${err?.message ?? err}`,\n            'Check that api.juejin.cn is reachable from this network.',\n        );\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'Juejin throttles bursty traffic; wait a few seconds and retry.',\n        );\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let payload;\n    try {\n        payload = await resp.json();\n    } catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {\n        throw new CommandExecutionError(`${label} returned a malformed API envelope`);\n    }\n    if (payload.err_no !== 0) {\n        throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);\n    }\n    return payload;\n}\n\nexport function readDataArray(payload, label) {\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'data')) {\n        throw new CommandExecutionError(`${label} returned a malformed payload`);\n    }\n    if (!Array.isArray(payload.data)) {\n        throw new CommandExecutionError(`${label} returned a non-array data field`);\n    }\n    if (payload.data.length === 0) {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/utils.js#L99-L135","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (raw body never inspected)\nconst payload = await juejinFetch('/recommend_api/feed/v1', body, 'juejin recommend');\n\n// after (caller-side diagnosis when this error repeats)\nconst resp = await fetch('https://api.juejin.cn/recommend_api/feed/v1', init);\nconst text = await resp.text();\ntry { JSON.parse(text); } catch { console.error('Non-JSON body:', text.slice(0, 300)); }","handlingStrategy":"retry","validationCode":"null","typeGuard":"function isMalformedJsonError(err) {\n  return err instanceof CommandExecutionError && /malformed JSON/.test(err.message);\n}","tryCatchPattern":"async function fetchJsonWithRetry(path, body, label, attempts = 3) {\n  for (let i = 0; ; i++) {\n    try { return await juejinFetch(path, body, label); }\n    catch (err) {\n      if (!isMalformedJsonError(err) || i >= attempts - 1) throw err;\n      await new Promise(r => setTimeout(r, 1000 * (i + 1))); // truncated bodies are often transient\n    }\n  }\n}","preventionTips":["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."],"tags":["json","parse-error","http-response","malformed-response"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}