jackwener/OpenCLI · error · CommandExecutionError
coingecko returned malformed JSON: ${error?.message || error
Error message
coingecko returned malformed JSON: ${error?.message || error} What it means
This CommandExecutionError is thrown when resp.json() fails to parse the HTTP response body, meaning the server returned something that is not valid JSON (despite a 2xx status). The parse error message is interpolated for diagnosis.
Source
Thrown at clis/coingecko/coin.js:66
try {
resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
} catch (error) {
throw new CommandExecutionError(`coingecko coin request failed: ${error?.message || error}`);
}
if (resp.status === 404) {
throw new EmptyResultError('coingecko coin', `coingecko has no coin with id "${id}".`);
}
if (resp.status === 429) {
throw new CommandExecutionError('coingecko returned HTTP 429 (rate limited)', 'Free tier allows ~30 calls/min. Wait and retry.');
}
if (!resp.ok) {
throw new CommandExecutionError(`coingecko coin failed: HTTP ${resp.status}`);
}
let data;
try {
data = await resp.json();
} catch (error) {
throw new CommandExecutionError(`coingecko returned malformed JSON: ${error?.message || error}`);
}
if (data?.error) {
throw new CommandExecutionError(`coingecko returned error: ${data.error}`);
}
const md = data.market_data || {};
const pick = (obj, key) => (obj && obj[key] != null ? obj[key] : null);
const isoFromMaybe = (s) => (s ? String(s).slice(0, 10) : '');
const price = pick(md.current_price, currency);
const marketCap = pick(md.market_cap, currency);
const volume24h = pick(md.total_volume, currency);
if (price == null && marketCap == null && volume24h == null) {
throw new CommandExecutionError(
`coingecko returned no market data for currency "${currency}"`,
'Use a CoinGecko-supported quote currency such as usd, cny, eur, or jpy.',
);
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Log or inspect the raw response text to see what was actually returned
- Check for proxy/captive-portal interference and retry from a different network
- Retry after a delay — often transient on the server side
Example fix
// before
data = await resp.json();
// after
const text = await resp.text();
try { data = JSON.parse(text); } catch (e) { throw new Error(`non-JSON body: ${text.slice(0, 200)}`); } Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
const looksLikeJson = (s) => { try { JSON.parse(s); return true; } catch { return false; } }; Try / catch
try { await run(['coingecko', 'coin', id]); } catch (e) { if (/malformed JSON/.test(e.message)) { await sleep(3000); return retryOnce(); } throw e; } Prevention
- Check for proxy/captive-portal interference if this recurs
- Retry transiently — HTML error pages under 200 are usually short-lived
- Verify the raw body with curl to distinguish environment issues from service issues
When it happens
Trigger: CoinGecko or an intermediary (proxy, captive portal, Cloudflare challenge page) returning HTML instead of JSON; truncated/garbled response bodies; content-encoding mismatches from MITM proxies.
Common situations: Corporate proxies rewriting responses; Wi-Fi captive portals intercepting the request; Cloudflare serving a challenge page to a flagged IP; CoinGecko maintenance returning an HTML error page with 200.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Xiaoyuzhou API returned invalid JSON: ${getErrorMessage(erro
- archive search returned malformed JSON: ${error?.message ||
- archive wayback returned malformed JSON: ${error?.message ||
- coingecko global returned no data envelope
- coingecko returned an unexpected response
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/1c8fb26dc314b4ca.
Report an issue: GitHub.