{"record":{"id":"16cb2aea40f4a6ec","repo":"jackwener/OpenCLI","slug":"malformed-json-from-stack-exchange-api-for-label","errorCode":null,"errorMessage":"Malformed JSON from Stack Exchange API for ${label}: ${detail}","messagePattern":"Malformed JSON from Stack Exchange API for (.+?): (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/stackoverflow/read.js","lineNumber":54,"sourceCode":"            `Network failure fetching ${label}: ${detail}`,\n            'Check connectivity to api.stackexchange.com',\n        );\n    }\n    if (res.status === 404) {\n        throw new EmptyResultError(label, `${label} not found`);\n    }\n    if (!res.ok) {\n        throw new CommandExecutionError(\n            `Stack Exchange API HTTP ${res.status} for ${label}`,\n            'Check the question id and quota (300/day per IP)',\n        );\n    }\n    let json;\n    try {\n        json = await res.json();\n    } catch (e) {\n        const detail = e instanceof Error ? e.message : String(e);\n        throw new CommandExecutionError(\n            `Malformed JSON from Stack Exchange API for ${label}: ${detail}`,\n            'The API returned a non-JSON body — likely a transient outage',\n        );\n    }\n    if (json && json.error_id) {\n        throw new CommandExecutionError(\n            `Stack Exchange API error ${json.error_id} (${json.error_name}) for ${label}: ${json.error_message || ''}`,\n            'Common causes: invalid filter, throttled, or quota exhausted',\n        );\n    }\n    return json;\n}\n\n/**\n * CLI args may arrive as strings (`--limit 5` → `'5'`) when not coerced by the\n * arg type system. Coerce-then-validate so `Number.isInteger` actually catches\n * the bad cases, and reject NaN explicitly.\n */","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/stackoverflow/read.js#L36-L72","documentation":"After a successful HTTP response, fetchJson parses the body with res.json(); if parsing throws it wraps the parse error detail in a CommandExecutionError. This means the API returned HTTP 200 (or at least non-404/non-error) but the body was not valid JSON, which the library interprets as a transient outage or an intermediary corrupting the response.","triggerScenarios":"res.json() rejects on any caller's response: an HTML error/maintenance page returned with status 200, a proxy or captive portal injecting a login page, truncated/garbled response body, or wrong Content-Type body from an intercepting middlebox.","commonSituations":"Corporate SSL-inspection proxies replacing the JSON with an HTML warning page; hotel/airport captive portals intercepting HTTPS; Stack Exchange returning a compressed or partial body during brief incidents; local MITM debugging tools breaking the stream.","solutions":["Retry the request — it is usually a transient outage or interception","Inspect the raw response: curl -s https://api.stackexchange.com/2.2/questions/<id>?site=stackoverflow | head -c 200 to see what is actually returned","Check whether a proxy/captive portal is intercepting the connection (look for HTML in the body)","Verify TLS is not being MITM'd by VPN/security software; bypass or trust its certificate"],"exampleFix":"async function safeFetchJson(url, label) {\n  const res = await fetch(url);\n  const text = await res.text();\n  try {\n    return JSON.parse(text);\n  } catch (e) {\n    throw new Error(`Non-JSON body for ${label}: ${text.slice(0, 120)}`);\n  }\n}","handlingStrategy":"try-catch","validationCode":"async function looksLikeJsonResponse(url) {\n  const res = await fetch(url);\n  const ct = res.headers.get('content-type') ?? '';\n  return ct.includes('application/json');\n}\n// warn if false before parsing: intercepting proxy or HTML error page likely","typeGuard":null,"tryCatchPattern":"try {\n  const data = await qData(id);\n} catch (e) {\n  if (/Malformed JSON from Stack Exchange API/.test(e?.message ?? '')) {\n    console.error('Non-JSON body — transient outage or proxy interference. Retrying in 30s...');\n    await new Promise(r => setTimeout(r, 30000));\n  } else {\n    throw e;\n  }\n}","preventionTips":["Retry on malformed JSON — it is usually transient, not a bug in your code","Bypass or properly configure TLS-inspecting proxies/captive portals","Spot-check raw responses with curl when errors cluster in time (outage signature)","Prefer reading res.text() then JSON.parse to get the offending body in your own error messages"],"tags":["json","parse-error","stackexchange-api","network","proxy"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}