jackwener/OpenCLI · error · CommandExecutionError

stack exchange returned malformed JSON: ${error?.message ||

Error message

stack exchange returned malformed JSON: ${error?.message || error}

What it means

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.

Source

Thrown at clis/stackoverflow/utils.js:74

                'User-Agent': UA,
            },
        });
    } catch (error) {
        throw new CommandExecutionError(`stack exchange request failed: ${error?.message || error}`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError('stack exchange returned HTTP 429 (rate limited)', 'Wait a few seconds and retry, or lower --limit.');
    }
    if (!resp.ok) {
        let body = '';
        try { body = (await resp.json())?.error_message || ''; } catch { /* ignore */ }
        throw new CommandExecutionError(`stack exchange HTTP ${resp.status}: ${body || resp.statusText}`);
    }
    let data;
    try {
        data = await resp.json();
    } catch (error) {
        throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}`);
    }
    if (data?.error_id) {
        throw new CommandExecutionError(
            `stack exchange API error: ${data.error_message || data.error_name}`,
            'Inspect the URL in a browser for the canonical error context.',
        );
    }
    return data;
}

/** Convert SE epoch seconds to YYYY-MM-DD. */
export function epochToDate(value) {
    if (value == null || value === '') return '';
    const n = Number(value);
    if (!Number.isFinite(n) || n <= 0) return '';
    return new Date(n * 1000).toISOString().slice(0, 10);
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command to rule out a transient proxy/captive-portal response
  2. Check connectivity: curl -sS 'https://api.stackexchange.com/2.3/sites?pagesize=1' and confirm JSON output
  3. Disable or bypass the corporate proxy/VPN for api.stackexchange.com
  4. Inspect the raw body (the preceding HTTP-status error would have shown it) to identify what is being returned instead of JSON

Example fix

// before
try {
  data = await resp.json();
} catch (error) {
  throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}`);
}
// after
const body = await resp.text();
let data;
try {
  data = JSON.parse(body);
} catch (error) {
  throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}; body started: ${body.slice(0, 120)}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const pre = await fetch(url, { method: 'HEAD' });
const ct = pre.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error(`expected JSON, got ${ct}`);

Type guard

function isJsonContentType(resp) {
  return (resp.headers.get('content-type') || '').includes('application/json');
}

Try / catch

try {
  const data = await seFetch(url);
} catch (e) {
  if (/malformed JSON/.test(e.message)) {
    // network/proxy returned non-JSON; surface body snippet and retry or abort
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/d813e687eaea3151. Report an issue: GitHub.