{"record":{"id":"8d72aa882949d378","repo":"jackwener/OpenCLI","slug":"coingecko-returned-malformed-json-error-messag-8d72aa","errorCode":null,"errorMessage":"coingecko returned malformed JSON: ${error?.message || error}","messagePattern":"coingecko returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/coingecko/trending.js","lineNumber":36,"sourceCode":"    func: async () => {\n        const url = 'https://api.coingecko.com/api/v3/search/trending';\n        let resp;\n        try {\n            resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n        } catch (error) {\n            throw new CommandExecutionError(`coingecko trending request failed: ${error?.message || error}`);\n        }\n        if (resp.status === 429) {\n            throw new CommandExecutionError('coingecko returned HTTP 429 (rate limited)', 'Wait and retry.');\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`coingecko trending failed: HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        } catch (error) {\n            throw new CommandExecutionError(`coingecko returned malformed JSON: ${error?.message || error}`);\n        }\n        const coins = Array.isArray(data?.coins) ? data.coins : [];\n        if (coins.length === 0) {\n            throw new EmptyResultError('coingecko trending', 'coingecko returned no trending coins.');\n        }\n        return coins.map((entry, i) => {\n            const c = entry?.item || {};\n            return {\n                rank: i + 1,\n                id: c.id || '',\n                symbol: String(c.symbol || '').toUpperCase(),\n                name: c.name || '',\n                marketCapRank: c.market_cap_rank ?? null,\n                priceBtc: c.price_btc ?? null,\n                thumb: c.thumb || c.small || c.large || '',\n            };\n        });\n    },","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/trending.js#L18-L54","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Inspect what the endpoint actually returns: curl -s https://api.coingecko.com/api/v3/search/trending | head -c 200 — HTML output means bot protection","Retry later or from a different network/IP if Cloudflare is challenging the spoofed Mozilla/5.0 User-Agent","Use CoinGecko's official API (with demo key) for reliable JSON responses","Add a defensive check in your wrapper: verify Content-Type is application/json before resp.json()"],"exampleFix":"// before\nconst resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\nconst data = await resp.json(); // may throw on HTML body\n// after\nconst ct = resp.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error(`unexpected content-type: ${ct}`);\nconst data = await resp.json();","handlingStrategy":"try-catch","validationCode":"const probe = await fetch('https://api.coingecko.com/api/v3/search/trending');\nconst ct = probe.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error(`non-JSON response: ${ct} — likely bot protection`);","typeGuard":"function isJsonParseError(e) {\n  return /malformed JSON|Unexpected token|JSON parse/i.test(e?.message || '');\n}","tryCatchPattern":"try {\n  const data = await runCli('coingecko trending');\n} catch (e) {\n  if (isJsonParseError(e)) {\n    const raw = await fetch(url).then(r => r.text());\n    console.error('non-JSON body:', raw.slice(0, 200)); // inspect HTML challenge/block page\n  }\n  throw e;\n}","preventionTips":["If the body is HTML, you're being challenged by Cloudflare — use the official API with a key","Verify content-type is application/json before parsing raw responses","Retry from a different IP/network if blocked","Keep library updated for API content-type changes"],"tags":["json","malformed-response","coingecko","cli"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}