{"record":{"id":"dcbe03478fd28924","repo":"jackwener/OpenCLI","slug":"label-returned-malformed-json-err-message-dcbe03","errorCode":null,"errorMessage":"${label} returned malformed JSON: ${err?.message ?? err}","messagePattern":"(.+?) returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/utils.js","lineNumber":128,"sourceCode":"    }\n\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `Semantic Scholar returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'Semantic Scholar throttles anonymous traffic; set SEMANTIC_SCHOLAR_API_KEY (free at https://www.semanticscholar.org/product/api) or wait a minute and retry.',\n        );\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let body;\n    try {\n        body = await resp.json();\n    } catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    if (body && typeof body === 'object' && body.error) {\n        throw new CommandExecutionError(`${label} returned an error: ${body.error}`);\n    }\n    return body;\n}\n\n/** Return the AI-generated one-line summary if present, else ''. */\nexport function tldrText(tldr) {\n    if (tldr && typeof tldr === 'object' && typeof tldr.text === 'string') {\n        return tldr.text.trim();\n    }\n    return '';\n}\n\n/** First author display name, or '' when authors is missing. */\nexport function firstAuthorName(authors) {\n    if (!Array.isArray(authors) || !authors.length) return '';","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/utils.js#L110-L146","documentation":"After a successful HTTP response, s2Fetch calls resp.json(); if the body is not valid JSON (truncated response, HTML error page, proxy interference), it throws this CommandExecutionError wrapping the underlying parse error message. It exists because Semantic Scholar sometimes serves non-JSON content on edge errors despite a 2xx/ok status.","triggerScenarios":"resp.ok is true but resp.json() rejects: the API or an intermediary returned HTML (login/captcha page), an empty body, or a truncated payload for a graph/v1 or recommendations/v1 endpoint.","commonSituations":"Corporate proxies or captive portals injecting HTML into 200 responses, CDN edge errors returning empty bodies, network interruption mid-download, or hitting a mirror that returns plain text.","solutions":["Retry the request — this is often transient.","Inspect the raw response text (curl the same URL with the same headers) to see what non-JSON content is returned.","Bypass proxies/VPNs that may rewrite the response; check for captive portals.","Verify the URL/fields are valid so the real API (not an error page) answers.","If persistent, report to Semantic Scholar or add a fallback that treats it as an outage."],"exampleFix":"// before: only JSON assumed\nconst body = await s2Fetch(url, 'paper');\n// after: pre-check content type and degrade gracefully\nconst resp = await fetch(url, { headers: { accept: 'application/json' } });\nconst text = await resp.text();\nlet body;\ntry { body = JSON.parse(text); }\ncatch { throw new Error(`Unexpected non-JSON response: ${text.slice(0, 120)}`); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isJsonObject(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const body = await s2Fetch(url, 'paper');\n} catch (err) {\n  if (/malformed JSON/.test(err.message)) {\n    // transient/proxy issue: retry once, then fail with context\n    return retryWithBackoff(() => s2Fetch(url, 'paper'), 2);\n  }\n  throw err;\n}","preventionTips":["Retry transient parse failures — they are usually momentary.","Bypass proxies/VPNs that may inject HTML into responses.","Send Accept: application/json (the adapter already does).","Check for captive portals when on restricted networks."],"tags":["json","parse-error","network","semantic-scholar"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}