jackwener/OpenCLI · error · CommandExecutionError
${label} returned malformed JSON: ${err?.message ?? err}
Error message
${label} returned malformed JSON: ${err?.message ?? err} What it means
readJson calls resp.json() on the OSV API response; if the body is not valid JSON (HTML error pages, truncated responses, proxies), it wraps the parse failure in a CommandExecutionError prefixed with the request label.
Source
Thrown at clis/osv/utils.js:92
export function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {
const raw = value ?? defaultValue;
const n = typeof raw === 'number' ? raw : Number(raw);
if (!Number.isInteger(n) || n <= 0) {
throw new ArgumentError(`osv ${label} must be a positive integer`);
}
if (n > maxValue) {
throw new ArgumentError(`osv ${label} must be <= ${maxValue}`);
}
return n;
}
async function readJson(resp, label) {
let body;
try {
body = await resp.json();
}
catch (err) {
throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
}
return body;
}
export async function osvGet(url, label) {
let resp;
try {
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
}
catch (err) {
throw new CommandExecutionError(
`${label} request failed: ${err?.message ?? err}`,
'Check that api.osv.dev is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `OSV.dev returned 404 for ${url}.`);
}View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the request; transient truncation is common.
- Check whether a proxy/firewall is intercepting api.osv.dev (curl the URL and inspect the body).
- Bypass or correctly configure proxy env vars (HTTP_PROXY/HTTPS_PROXY).
- Capture the full response body to see what was actually returned.
Example fix
// before
const body = await readJson(resp, label); // throws on HTML body
// after
try {
const body = await readJson(resp, label);
} catch (e) {
console.error('Non-JSON response from OSV; check proxy/network:', e.message);
} Defensive patterns
Strategy: retry
Validate before calling
const resp = await fetch(url);
const text = await resp.text();
try { JSON.parse(text); } catch { console.error('Non-JSON response, first 200 chars:', text.slice(0, 200)); } Type guard
const isJsonObject = (v) =>
typeof v === 'object' && v !== null && !Array.isArray(v) &&
!Number.isNaN(Date.parse('x')) === false; // use JSON.parse in practice
// practical guard:
const parsesAsJson = (text) => { try { JSON.parse(text); return true; } catch { return false; } }; Try / catch
try {
const body = await osvGet(url, label);
} catch (e) {
if (e instanceof CommandExecutionError && /malformed JSON/.test(e.message)) {
console.error('OSV returned non-JSON (proxy/HTML page?) — inspect network path and retry');
return retryWithBackoff(() => osvGet(url, label), 3);
}
throw e;
} Prevention
- Probe api.osv.dev with curl to detect proxy/captive-portal interference.
- Configure HTTPS_PROXY/NO_PROXY correctly in CI and corporate networks.
- Retry transient failures with exponential backoff.
- Log the raw response body on JSON parse failure for diagnosis.
When it happens
Trigger: api.osv.dev or an intermediary (corporate proxy, captive portal, Cloudflare block page) returns non-JSON content with a 2xx/3xx status; response body truncated mid-stream.
Common situations: Corporate proxy injecting an HTML login page; rate limiting returning an HTML error; DNS hijacking; network flakiness truncating the body.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- 12306 queryByTrainNo returned non-JSON body
- 12306 ${endpoint} returned non-JSON body
- coingecko derivatives returned malformed JSON: ${err?.messag
- coingecko global returned malformed JSON: ${err?.message ??
- coingecko returned malformed JSON: ${error?.message || error
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/23984ef8a4305093.
Report an issue: GitHub.