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

  1. Log or inspect the raw response text to see what was actually returned
  2. Check for proxy/captive-portal interference and retry from a different network
  3. 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

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.

Related errors


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