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 CoinGecko trending response body cannot be parsed as JSON — resp.json() rejects. The library wraps the parse error and prefixes context so it's clear which endpoint returned the malformed body. Commonly this means the server returned an HTML page (Cloudflare challenge, error page, proxy block page) instead of JSON.

Source

Thrown at clis/coingecko/trending.js:36

    func: async () => {
        const url = 'https://api.coingecko.com/api/v3/search/trending';
        let resp;
        try {
            resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
        } catch (error) {
            throw new CommandExecutionError(`coingecko trending request failed: ${error?.message || error}`);
        }
        if (resp.status === 429) {
            throw new CommandExecutionError('coingecko returned HTTP 429 (rate limited)', 'Wait and retry.');
        }
        if (!resp.ok) {
            throw new CommandExecutionError(`coingecko trending failed: HTTP ${resp.status}`);
        }
        let data;
        try {
            data = await resp.json();
        } catch (error) {
            throw new CommandExecutionError(`coingecko returned malformed JSON: ${error?.message || error}`);
        }
        const coins = Array.isArray(data?.coins) ? data.coins : [];
        if (coins.length === 0) {
            throw new EmptyResultError('coingecko trending', 'coingecko returned no trending coins.');
        }
        return coins.map((entry, i) => {
            const c = entry?.item || {};
            return {
                rank: i + 1,
                id: c.id || '',
                symbol: String(c.symbol || '').toUpperCase(),
                name: c.name || '',
                marketCapRank: c.market_cap_rank ?? null,
                priceBtc: c.price_btc ?? null,
                thumb: c.thumb || c.small || c.large || '',
            };
        });
    },

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect what the endpoint actually returns: curl -s https://api.coingecko.com/api/v3/search/trending | head -c 200 — HTML output means bot protection
  2. Retry later or from a different network/IP if Cloudflare is challenging the spoofed Mozilla/5.0 User-Agent
  3. Use CoinGecko's official API (with demo key) for reliable JSON responses
  4. Add a defensive check in your wrapper: verify Content-Type is application/json before resp.json()

Example fix

// before
const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
const data = await resp.json(); // may throw on HTML body
// after
const ct = resp.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error(`unexpected content-type: ${ct}`);
const data = await resp.json();
Defensive patterns

Strategy: try-catch

Validate before calling

const probe = await fetch('https://api.coingecko.com/api/v3/search/trending');
const ct = probe.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error(`non-JSON response: ${ct} — likely bot protection`);

Type guard

function isJsonParseError(e) {
  return /malformed JSON|Unexpected token|JSON parse/i.test(e?.message || '');
}

Try / catch

try {
  const data = await runCli('coingecko trending');
} catch (e) {
  if (isJsonParseError(e)) {
    const raw = await fetch(url).then(r => r.text());
    console.error('non-JSON body:', raw.slice(0, 200)); // inspect HTML challenge/block page
  }
  throw e;
}

Prevention

When it happens

Trigger: `await resp.json()` in the trending command throws a SyntaxError ('Unexpected token < in JSON', etc.) because the body of the HTTP 200 (or other ok) response is not valid JSON — e.g. an HTML bot-challenge page, an empty body, or a truncated response.

Common situations: Cloudflare/WAF serving an HTML challenge to the spoofed User-Agent request, corporate proxy injecting an HTML block page with status 200, network truncation mid-body, or CoinGecko API changes changing the content type.

Understand the failure class

Related errors


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