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 the /coins/markets response body cannot be parsed as JSON, even though an HTTP response arrived. The library wraps resp.json() failures so the underlying parse error is preserved in the message. It usually means the body was HTML (Cloudflare/blocked page) or truncated.

Source

Thrown at clis/coingecko/top.js:46

    const url = new URL('https://api.coingecko.com/api/v3/coins/markets');
    url.searchParams.set('vs_currency', currency);
    url.searchParams.set('order', 'market_cap_desc');
    url.searchParams.set('per_page', String(limit));
    url.searchParams.set('page', '1');
    url.searchParams.set('sparkline', 'false');

    let resp;
    try {
      resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
    } catch (error) {
      throw new CommandExecutionError(`coingecko top request failed: ${error?.message || error}`);
    }
    if (!resp.ok) throw new CommandExecutionError(`coingecko top 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}`);
    if (!Array.isArray(data)) throw new CommandExecutionError('coingecko returned an unexpected response');
    if (data.length === 0) throw new EmptyResultError('coingecko top', 'coingecko returned no market data');

    return data.map((c) => ({
      rank: c.market_cap_rank,
      symbol: String(c.symbol ?? '').toUpperCase(),
      name: c.name,
      price: c.current_price,
      change24hPct: c.price_change_percentage_24h,
      marketCap: c.market_cap,
      volume24h: c.total_volume,
      high24h: c.high_24h,
      low24h: c.low_24h,
    }));
  },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw response with curl to confirm whether it is HTML or truncated JSON.
  2. Retry with backoff — often transient.
  3. Use a CoinGecko Pro API endpoint/key or a different exit IP if Cloudflare keeps challenging.
  4. Disable or bypass interfering proxies/SSL inspection for api.coingecko.com.
  5. Add a browser-like User-Agent/Accept headers if your environment strips them.

Example fix

// before
const rows = await runCli('coingecko', 'top');
// after
let rows; try { rows = await runCli('coingecko', 'top'); }
catch (e) {
  if (String(e.message).includes('malformed JSON')) { await sleep(2000); rows = await runCli('coingecko', 'top'); }
  else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

const r = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=1', { headers: { 'User-Agent': 'Mozilla/5.0' } });
const ct = r.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error('coingecko not returning JSON (got ' + ct + ')');

Type guard

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

Try / catch

try {
  rows = await runCli('coingecko', 'top');
} catch (e) {
  if (String(e.message).includes('malformed JSON')) {
    await new Promise(r => setTimeout(r, 3000));
    rows = await runCli('coingecko', 'top');
  } else throw e;
}

Prevention

When it happens

Trigger: Cloudflare returning an HTML challenge with 200; a captive portal or proxy injecting HTML; response body truncated mid-transfer; CoinGecko returning an empty body on some edge errors.

Common situations: Datacenter/VPN IPs flagged by Cloudflare; corporate SSL-inspection proxies rewriting bodies; flaky mobile networks truncating responses; scripted bursts triggering bot protection.

Understand the failure class

Related errors


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