{"record":{"id":"020e573504c48475","repo":"jackwener/OpenCLI","slug":"coingecko-categories-returned-malformed-json-er","errorCode":null,"errorMessage":"coingecko categories returned malformed JSON: ${err?.message ?? err}","messagePattern":"coingecko categories returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/coingecko/categories.js","lineNumber":60,"sourceCode":"        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko categories request failed: ${err?.message ?? err}`);\n        }\n        if (resp.status === 429) {\n            throw new CommandExecutionError(\n                'coingecko returned HTTP 429 (rate limited)',\n                'Free tier allows ~30 calls/min. Wait and retry.',\n            );\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`coingecko categories returned HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko categories returned malformed JSON: ${err?.message ?? err}`);\n        }\n        if (!Array.isArray(data) || !data.length) {\n            throw new EmptyResultError('coingecko categories', 'CoinGecko returned no category data.');\n        }\n        return data.slice(0, limit).map((cat, i) => ({\n            rank: i + 1,\n            id: String(cat.id ?? ''),\n            name: String(cat.name ?? ''),\n            marketCap: cat.market_cap != null ? Number(cat.market_cap) : null,\n            volume24h: cat.volume_24h != null ? Number(cat.volume_24h) : null,\n            marketCapChange24hPct: cat.market_cap_change_24h != null ? Number(cat.market_cap_change_24h) : null,\n            top3Coins: Array.isArray(cat.top_3_coins_id) ? cat.top_3_coins_id.join(', ') : '',\n        }));\n    },\n});\n","sourceCodeStart":42,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/categories.js#L42-L76","documentation":"The HTTP response body could not be parsed as JSON; the CLI catches resp.json() failure and wraps it in a CommandExecutionError with the parse error. Typically the API returned HTML (error page, challenge page) instead of JSON.","triggerScenarios":"resp.json() throws because the body is HTML/text — e.g. a Cloudflare challenge, gateway error page, or truncated response from api.coingecko.com.","commonSituations":"CoinGecko outage returning an HTML error page; ISP/proxy interception page; CDN challenge (Cloudflare) triggered by rate or bot detection.","solutions":["Retry after a delay — often transient","Check whether a proxy/CDN is intercepting the response and bypass it","Inspect the raw response (curl the URL) to see what body is actually returned"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// verify the endpoint returns JSON before relying on it\nconst probe = await fetch('https://api.coingecko.com/api/v3/coins/categories?limit=1');\nconst ct = probe.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error('CoinGecko not returning JSON');","typeGuard":"function isJsonArray(v) {\n  return Array.isArray(v);\n}","tryCatchPattern":"try {\n  await runCategories(args);\n} catch (err) {\n  if (err instanceof CommandExecutionError && /malformed JSON/.test(err.message)) {\n    await sleep(5000);\n    return runCategories(args); // often a transient HTML error page\n  }\n  throw err;\n}","preventionTips":["Check content-type before parsing JSON in custom clients","Bypass intercepting proxies/VPN that inject HTML pages","Space out requests to avoid Cloudflare-style challenges"],"tags":["json-parse","malformed-response","network","coingecko"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}