{"record":{"id":"82deb051becac09d","repo":"Hmbown/CodeWhale","slug":"cf-account-id-and-cf-api-token-are-required","errorCode":null,"errorMessage":"CF_ACCOUNT_ID and CF_API_TOKEN are required","messagePattern":"CF_ACCOUNT_ID and CF_API_TOKEN are required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"telemetry-ingest/scripts/report-active-installs.mjs","lineNumber":220,"sourceCode":"    headers: {\n      Authorization: `Bearer ${apiToken}`,\n      \"content-type\": \"text/plain; charset=utf-8\",\n    },\n    body: sql,\n  });\n  if (!response.ok) {\n    const body = (await response.text()).slice(0, 500);\n    throw new Error(`Cloudflare SQL request failed (${response.status}): ${body}`);\n  }\n  return response.json();\n}\n\nexport async function main(argv = process.argv.slice(2), env = process.env) {\n  const { days, json } = parseArgs(argv);\n  const accountId = env.CF_ACCOUNT_ID?.trim();\n  const apiToken = env.CF_API_TOKEN?.trim();\n  if (!accountId || !apiToken) {\n    throw new Error(\"CF_ACCOUNT_ID and CF_API_TOKEN are required\");\n  }\n  const rows = rowsFromResponse(\n    await querySql({ accountId, apiToken, sql: activeInstallsSql(days) }),\n  );\n  const newestEvent = newestEventFromResponse(\n    await querySql({ accountId, apiToken, sql: freshnessSql() }),\n  );\n  const now = new Date();\n  if (json) {\n    process.stdout.write(\n      `${JSON.stringify(\n        {\n          metric: \"observed_active_installs\",\n          timezone: \"UTC\",\n          days,\n          rows,\n          trend: trendSummary(rows, days, now),\n          freshness: {","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/telemetry-ingest/scripts/report-active-installs.mjs#L202-L238","documentation":"main() requires Cloudflare credentials before issuing any D1 queries. It reads CF_ACCOUNT_ID and CF_API_TOKEN from the environment, trims them, and throws if either is missing or empty, because the script cannot authenticate to the Cloudflare API without both.","triggerScenarios":"Running `report-active-installs` without exporting CF_ACCOUNT_ID or CF_API_TOKEN, or with either set to whitespace-only or empty string.","commonSituations":"Forgetting to source an .env file; running under CI where secrets were not injected; typo in variable name (e.g. CLOUDFLARE_API_TOKEN); shell where `export` was omitted.","solutions":["Export both variables before running: CF_ACCOUNT_ID=... CF_API_TOKEN=... node script.","Add them to your .env / CI secret store and ensure it is actually loaded.","Check for typos and whitespace: `echo \"[$CF_ACCOUNT_ID]\"` should show non-empty content.","Use a Cloudflare API token scoped to D1 read for the correct account."],"exampleFix":"// before\nnode telemetry-ingest/scripts/report-active-installs.mjs --days 30\n// after\nCF_ACCOUNT_ID=abc123 CF_API_TOKEN=xxxx node telemetry-ingest/scripts/report-active-installs.mjs --days 30","handlingStrategy":"validation","validationCode":"if (!process.env.CF_ACCOUNT_ID?.trim() || !process.env.CF_API_TOKEN?.trim()) { console.error(\"Set CF_ACCOUNT_ID and CF_API_TOKEN first\"); process.exit(1); }","typeGuard":null,"tryCatchPattern":"try { await main(); } catch (e) { if (e.message.includes(\"CF_ACCOUNT_ID\")) { console.error(\"Missing Cloudflare credentials: export CF_ACCOUNT_ID and CF_API_TOKEN\"); process.exitCode = 2; } else throw e; }","preventionTips":["Load .env before running (dotenv or `set -a; source .env; set +a`).","Add a preflight credential check in CI before invoking the script.","Use consistent variable names across scripts.","Echo variables (masked) to confirm they are non-empty in CI."],"tags":["environment","cloudflare","credentials","config"],"backgroundTag":"missing-env-var","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}