{"record":{"id":"d813e687eaea3151","repo":"jackwener/OpenCLI","slug":"stack-exchange-returned-malformed-json-error-m","errorCode":null,"errorMessage":"stack exchange returned malformed JSON: ${error?.message || error}","messagePattern":"stack exchange returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/stackoverflow/utils.js","lineNumber":74,"sourceCode":"                'User-Agent': UA,\n            },\n        });\n    } catch (error) {\n        throw new CommandExecutionError(`stack exchange request failed: ${error?.message || error}`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError('stack exchange returned HTTP 429 (rate limited)', 'Wait a few seconds and retry, or lower --limit.');\n    }\n    if (!resp.ok) {\n        let body = '';\n        try { body = (await resp.json())?.error_message || ''; } catch { /* ignore */ }\n        throw new CommandExecutionError(`stack exchange HTTP ${resp.status}: ${body || resp.statusText}`);\n    }\n    let data;\n    try {\n        data = await resp.json();\n    } catch (error) {\n        throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}`);\n    }\n    if (data?.error_id) {\n        throw new CommandExecutionError(\n            `stack exchange API error: ${data.error_message || data.error_name}`,\n            'Inspect the URL in a browser for the canonical error context.',\n        );\n    }\n    return data;\n}\n\n/** Convert SE epoch seconds to YYYY-MM-DD. */\nexport function epochToDate(value) {\n    if (value == null || value === '') return '';\n    const n = Number(value);\n    if (!Number.isFinite(n) || n <= 0) return '';\n    return new Date(n * 1000).toISOString().slice(0, 10);\n}\n","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/stackoverflow/utils.js#L56-L92","documentation":"seFetch parses the Stack Exchange API response with resp.json(); when the body is not valid JSON it wraps the parse failure in a CommandExecutionError with the underlying parser message. The Stack Exchange API normally always returns JSON, so this usually means a proxy, captive portal, or HTML error page intercepted the response.","triggerScenarios":"Network intermediaries return an HTML page (login portal, 502 from a proxy/CDN) with HTTP 200; the API returns an empty body; a wrong or redirecting URL yields HTML instead of JSON.","commonSituations":"Corporate proxy or hotel/airport wifi captive portal intercepting requests to api.stackexchange.com; rate limiting handled by an edge that serves HTML; TLS interception appliances rewriting the response.","solutions":["Re-run the command to rule out a transient proxy/captive-portal response","Check connectivity: curl -sS 'https://api.stackexchange.com/2.3/sites?pagesize=1' and confirm JSON output","Disable or bypass the corporate proxy/VPN for api.stackexchange.com","Inspect the raw body (the preceding HTTP-status error would have shown it) to identify what is being returned instead of JSON"],"exampleFix":"// before\ntry {\n  data = await resp.json();\n} catch (error) {\n  throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}`);\n}\n// after\nconst body = await resp.text();\nlet data;\ntry {\n  data = JSON.parse(body);\n} catch (error) {\n  throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}; body started: ${body.slice(0, 120)}`);\n}","handlingStrategy":"try-catch","validationCode":"const pre = await fetch(url, { method: 'HEAD' });\nconst ct = pre.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error(`expected JSON, got ${ct}`);","typeGuard":"function isJsonContentType(resp) {\n  return (resp.headers.get('content-type') || '').includes('application/json');\n}","tryCatchPattern":"try {\n  const data = await seFetch(url);\n} catch (e) {\n  if (/malformed JSON/.test(e.message)) {\n    // network/proxy returned non-JSON; surface body snippet and retry or abort\n  } else throw e;\n}","preventionTips":["Verify api.stackexchange.com returns JSON with curl before scripting","Bypass corporate proxies/VPN or set NO_PROXY for the API host","Log the raw response body on parse failure to identify interceptors","Retry once on transient gateway errors"],"tags":["network","json","http","proxy"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}