{"record":{"id":"1d91e38deae48c07","repo":"koala73/worldmonitor","slug":"cloudflare-radar-source-result-field-is-missing-or-not-an","errorCode":null,"errorMessage":"Cloudflare Radar ${source}: result.${field} is missing or not an array","messagePattern":"Cloudflare Radar (.+?): result\\.(.+?) is missing or not an array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-internet-outages.mjs","lineNumber":118,"sourceCode":"  // to wait out. One shared message would make a Railway log line say which\n  // endpoint failed but not which of those two it was.\n  const reason = (() => {\n    if (!data || typeof data !== 'object' || Array.isArray(data)) return 'body is not a JSON object';\n    if (data.configured === false) return 'not configured for this token';\n    if (data.success !== true) return `success=${JSON.stringify(data.success)}`;\n    if (data.errors != null && !Array.isArray(data.errors)) return 'errors field is not an array';\n    if (Array.isArray(data.errors) && data.errors.length > 0) return `errors=${JSON.stringify(data.errors).slice(0, 200)}`;\n    if (!data.result || typeof data.result !== 'object' || Array.isArray(data.result)) return 'result is missing or not an object';\n    return null;\n  })();\n  if (reason) throw new Error(`Cloudflare Radar ${source}: invalid success envelope (${reason})`);\n  return data.result;\n}\n\n/** Require an array-valued result field — an absent one is a failure, not zero records. */\nfunction requireRadarArray(result, field, source) {\n  if (!Array.isArray(result[field])) {\n    throw new Error(`Cloudflare Radar ${source}: result.${field} is missing or not an array`);\n  }\n  return result[field];\n}\n\n/** Require an object-valued result field (Radar summary maps). */\nfunction requireRadarObject(result, field, source) {\n  const value = result[field];\n  if (!value || typeof value !== 'object' || Array.isArray(value)) {\n    throw new Error(`Cloudflare Radar ${source}: result.${field} is missing or not an object`);\n  }\n  return value;\n}\n\nasync function fetchOutages() {\n  const token = process.env.CLOUDFLARE_API_TOKEN;\n  if (!token) {\n    console.log('CLOUDFLARE_API_TOKEN not set — skipping');\n    process.exit(0);","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-internet-outages.mjs#L100-L136","documentation":"requireRadarArray() in the Cloudflare Radar seeding script enforces that a Cloudflare Radar API response contains an array under the expected result field (e.g. result.annotations). The Cloudflare API omits or nulls fields it has no data for; the script deliberately treats a missing array as a hard failure instead of silently seeding zero records, because an empty/absent field usually signals a wrong endpoint, changed API schema, or broken auth rather than a true empty dataset.","triggerScenarios":"fetchOutages/fetch summaries calls requireRadarArray(result, field, source) after resp.json(); the API returns {result: {}} or {result: {annotations: null}} because the Radar schema changed, the wrong product endpoint was used, or the account lacks access to that Radar dataset.","commonSituations":"Cloudflare Radar API schema/field renames after an API version bump; using an API token without Radar read permissions so result is an empty object; hitting a summary endpoint expecting a different field name; expired/limited free-tier responses.","solutions":["Log the raw JSON response and compare the actual field names against the Radar API docs for your API version; update the field name in the seed script if Radar renamed it.","Check the API token has Cloudflare Radar read permission and that the account/plan includes Radar access.","Confirm the Radar API version path (e.g. /client/v4/radar/...) still serves the endpoint and pass the expected version header.","If a legitimately empty dataset is possible for your account, decide explicitly: skip the seed with a warning instead of asserting, but never treat it as normal silently."],"exampleFix":"// before\nconst annotations = requireRadarArray(result, 'annotations', 'outage annotations');\n// after\nconst annotations = Array.isArray(result.annotations)\n  ? result.annotations\n  : (console.warn(`Radar outage annotations missing; resp=${JSON.stringify(result).slice(0, 300)}`), []); // or keep requireRadarArray and fix the endpoint/token","handlingStrategy":"type-guard","validationCode":"function expectRadarShape(apiJson, field) {\n  if (apiJson.errors?.length) throw new Error(`Radar API errors: ${JSON.stringify(apiJson.errors)}`);\n  const result = apiJson?.result;\n  if (!result || typeof result !== 'object' || !Array.isArray(result[field])) {\n    throw new Error(`Radar response missing array result.${field}: ${JSON.stringify(apiJson).slice(0, 300)}`);\n  }\n  return result[field];\n}","typeGuard":"const hasRadarArray = (result, field) => !!result && typeof result === 'object' && Array.isArray(result[field]);","tryCatchPattern":"try {\n  const annotations = requireRadarArray(result, 'annotations', 'outage annotations');\n} catch (e) {\n  if (e.message.includes('missing or not an array')) {\n    console.error('Radar schema drift or access issue; raw:', JSON.stringify(result).slice(0, 500));\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Pin and monitor the Radar API version; re-run the seed after any Cloudflare Radar API changelog update.","Log the raw JSON body on validation failure for immediate schema comparison.","Check Cloudflare's errors array before shape validation so auth/quota issues are reported as such.","Grant the API token explicit Radar read scope and verify with a curl smoke test after rotation."],"tags":["cloudflare","api","schema-change","seeding"],"backgroundTag":"unexpected-response-shape","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}