{"record":{"id":"4990777f0fabb6d4","repo":"jackwener/OpenCLI","slug":"label-returned-malformed-json-err-message-499077","errorCode":null,"errorMessage":"${label} returned malformed JSON: ${err?.message ?? err}","messagePattern":"(.+?) returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/tvmaze/utils.js","lineNumber":68,"sourceCode":"    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `TVmaze returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'TVmaze caps unauthenticated traffic at ~20 req/10s; wait a few seconds 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    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\nconst HTML_ENTITY_MAP = {\n    amp: '&',\n    lt: '<',\n    gt: '>',\n    quot: '\"',\n    apos: \"'\",\n    nbsp: ' ',\n    rsquo: '’',\n    lsquo: '‘',\n    rdquo: '”',\n    ldquo: '“',\n    hellip: '…',\n    ndash: '–',\n    mdash: '—',","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tvmaze/utils.js#L50-L86","documentation":"After a successful HTTP response, tvmazeFetch parses the body with resp.json(). If the body is not valid JSON (HTML error page, empty body, truncated response), it throws a CommandExecutionError including the underlying parse error message. This indicates the endpoint responded but not with the JSON the adapter expects.","triggerScenarios":"list or show commands receive a response whose body fails JSON.parse — e.g. a proxy returning HTML, an empty 200 response, or a captive-portal interception page.","commonSituations":"Corporate proxies or VPNs intercepting api.tvmaze.com traffic, DNS hijacking to a landing page, an intermediate caching layer serving stale/truncated content, or TVmaze briefly serving an error page with a 200 status.","solutions":["Inspect the underlying parse message — an 'Unexpected token <' usually means HTML was returned instead of JSON","Check for proxy/VPN/DNS interception of api.tvmaze.com (curl -s https://api.tvmaze.com/shows/1 to see raw output)","Retry the command; transient truncation may resolve","Disable TLS-intercepting middleboxes for api.tvmaze.com"],"exampleFix":"// before\ntry { body = await resp.json(); } catch (err) { throw ... }\n// after (caller-side pre-check)\nconst text = await resp.text();\nif (!text.trim().startsWith('{') && !text.trim().startsWith('[')) {\n  throw new CommandExecutionError(`Unexpected non-JSON body: ${text.slice(0, 80)}`);\n}\nbody = JSON.parse(text);","handlingStrategy":"validation","validationCode":"const text = await resp.text();\nif (!text.trim()) throw new Error('empty response body');\nJSON.parse(text); // fail fast with your own message before handing to the adapter","typeGuard":"function looksLikeJson(text) { const t = text.trim(); return t.startsWith('{') || t.startsWith('['); }","tryCatchPattern":"try {\n  const body = await tvmazeFetch(url, 'search');\n} catch (err) {\n  if (err.message.includes('malformed JSON')) {\n    console.error('Non-JSON body from api.tvmaze.com — check proxy/DNS interception');\n    return null;\n  }\n  throw err;\n}","preventionTips":["Curl api.tvmaze.com once to confirm raw JSON arrives at your machine","Exclude the API host from TLS-intercepting proxies/VPNs","Handle empty 200 bodies defensively","Retry transient truncation errors"],"tags":["json","parsing","network"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}