{"record":{"id":"29f5a99cea570512","repo":"koala73/worldmonitor","slug":"coingecko-returned-no-data","errorCode":null,"errorMessage":"CoinGecko returned no data","messagePattern":"CoinGecko returned no data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/seed-token-panels.mjs","lineNumber":45,"sourceCode":"export const COINGECKO_RETRY_BUDGET_MS = 45_000;\n// The fallback runs COINPAPRIKA_IDS in rounds of COINPAPRIKA_CONCURRENCY, each\n// round bounded by one request timeout. Both values are passed to the helper\n// explicitly so this derivation describes the call as made, and mapping a new\n// token widens the budget instead of silently breaking the invariant.\nconst COINPAPRIKA_CONCURRENCY = 4;\nexport const COINPAPRIKA_WORST_CASE_MS = Math.ceil(COINPAPRIKA_IDS.length / COINPAPRIKA_CONCURRENCY) * REQUEST_TIMEOUT_MS;\n\nasync function fetchFromCoinGecko() {\n  const { baseUrl, headers } = coingeckoEndpoint();\n  const url = `${baseUrl}/coins/markets?vs_currency=usd&ids=${ALL_IDS.join(',')}&order=market_cap_desc&sparkline=false&price_change_percentage=24h,7d`;\n\n  const resp = await fetchCoinGeckoWithRetryBudget(url, {\n    headers,\n    requestTimeoutMs: REQUEST_TIMEOUT_MS,\n    budgetMs: COINGECKO_RETRY_BUDGET_MS,\n  });\n  const data = await resp.json();\n  if (!Array.isArray(data) || data.length === 0) throw new Error('CoinGecko returned no data');\n  return data;\n}\n\nasync function fetchFromCoinPaprika() {\n  console.log('  [CoinPaprika] Falling back to CoinPaprika...');\n  const tickers = await fetchCoinPaprikaTickersById(COINPAPRIKA_IDS, {\n    concurrency: COINPAPRIKA_CONCURRENCY,\n    timeoutMs: REQUEST_TIMEOUT_MS,\n  });\n  const reverseMap = new Map(Object.entries(COINPAPRIKA_ID_MAP).map(([g, p]) => [p, g]));\n  return tickers\n    .map((t) => ({\n      id: reverseMap.get(t.id) || t.id,\n      current_price: t.quotes.USD.price,\n      price_change_percentage_24h: t.quotes.USD.percent_change_24h,\n      price_change_percentage_7d_in_currency: t.quotes.USD.percent_change_7d,\n      symbol: t.symbol.toLowerCase(),\n      name: t.name,","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/scripts/seed-token-panels.mjs#L27-L63","documentation":"fetchFromCoinGecko in the token-panel seeding script validates that the CoinGecko markets endpoint returned a non-empty JSON array. If the parsed body is not an array or has zero items, the script throws so the CoinPaprika fallback can be attempted. It guards against silently seeding empty panel data when CoinGecko returns an empty or differently-shaped response.","triggerScenarios":"The retry-budgeted GET to CoinGecko succeeded with HTTP 200 but the JSON body parsed to something other than an array (e.g. {\"status\":{\"error_code\":...}} rate-limit/pro limit payload) or an empty array (no tokens matched the query).","commonSituations":"CoinGecko free-tier rate limiting returning 200-with-error-body, a bad/unknown query yielding zero markets rows, CoinGecko API shape changes, or an outage returning an empty result set.","solutions":["Retry later or check CoinGecko rate-limit/pro-plan status; the error body in data usually explains the throttle.","Verify the CoinGecko request URL/ids parameters actually match listed tokens.","Let the script proceed to the built-in CoinPaprika fallback instead of hard-requiring CoinGecko.","Cache last good seed data so transient empty responses don't wipe panels."],"exampleFix":"// before\nconst data = await resp.json();\nif (!Array.isArray(data) || data.length === 0) throw new Error('CoinGecko returned no data');\n// after\nconst data = await resp.json().catch(() => null);\nif (!Array.isArray(data) || data.length === 0) {\n  console.warn('CoinGecko returned no usable data; using fallback');\n  return fetchFromCoinPaprika();\n}","handlingStrategy":"fallback","validationCode":"const probe = await resp.json().catch(() => null);\nif (!Array.isArray(probe) || probe.length === 0) return fetchFromCoinPaprika();","typeGuard":"function isTickerArray(v) { return Array.isArray(v) && v.every(t => t && typeof t.id === 'string'); }","tryCatchPattern":"try { return await fetchFromCoinGecko(); } catch (err) { console.warn('CoinGecko unavailable:', err.message); return await fetchFromCoinPaprika(); }","preventionTips":["Respect CoinGecko free-tier rate limits with caching and staggering.","Log the raw response body when shape validation fails to diagnose throttles.","Always keep a working secondary data source wired in."],"tags":["network","external-api","empty-response"],"backgroundTag":"empty-result-set","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}