koala73/worldmonitor · error · Error

Cloudflare Radar ${source}: result.${field} is missing or no

Error message

Cloudflare Radar ${source}: result.${field} is missing or not an array

What it means

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.

Solutions

  1. 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.
  2. Check the API token has Cloudflare Radar read permission and that the account/plan includes Radar access.
  3. Confirm the Radar API version path (e.g. /client/v4/radar/...) still serves the endpoint and pass the expected version header.
  4. 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.

Example fix

// before
const annotations = requireRadarArray(result, 'annotations', 'outage annotations');
// after
const annotations = Array.isArray(result.annotations)
  ? result.annotations
  : (console.warn(`Radar outage annotations missing; resp=${JSON.stringify(result).slice(0, 300)}`), []); // or keep requireRadarArray and fix the endpoint/token
Defensive patterns

Strategy: type-guard

Validate before calling

function expectRadarShape(apiJson, field) {
  if (apiJson.errors?.length) throw new Error(`Radar API errors: ${JSON.stringify(apiJson.errors)}`);
  const result = apiJson?.result;
  if (!result || typeof result !== 'object' || !Array.isArray(result[field])) {
    throw new Error(`Radar response missing array result.${field}: ${JSON.stringify(apiJson).slice(0, 300)}`);
  }
  return result[field];
}

Type guard

const hasRadarArray = (result, field) => !!result && typeof result === 'object' && Array.isArray(result[field]);

Try / catch

try {
  const annotations = requireRadarArray(result, 'annotations', 'outage annotations');
} catch (e) {
  if (e.message.includes('missing or not an array')) {
    console.error('Radar schema drift or access issue; raw:', JSON.stringify(result).slice(0, 500));
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/1d91e38deae48c07. Report an issue: GitHub.

Appendix: source

Thrown at scripts/seed-internet-outages.mjs:118

  // to wait out. One shared message would make a Railway log line say which
  // endpoint failed but not which of those two it was.
  const reason = (() => {
    if (!data || typeof data !== 'object' || Array.isArray(data)) return 'body is not a JSON object';
    if (data.configured === false) return 'not configured for this token';
    if (data.success !== true) return `success=${JSON.stringify(data.success)}`;
    if (data.errors != null && !Array.isArray(data.errors)) return 'errors field is not an array';
    if (Array.isArray(data.errors) && data.errors.length > 0) return `errors=${JSON.stringify(data.errors).slice(0, 200)}`;
    if (!data.result || typeof data.result !== 'object' || Array.isArray(data.result)) return 'result is missing or not an object';
    return null;
  })();
  if (reason) throw new Error(`Cloudflare Radar ${source}: invalid success envelope (${reason})`);
  return data.result;
}

/** Require an array-valued result field — an absent one is a failure, not zero records. */
function requireRadarArray(result, field, source) {
  if (!Array.isArray(result[field])) {
    throw new Error(`Cloudflare Radar ${source}: result.${field} is missing or not an array`);
  }
  return result[field];
}

/** Require an object-valued result field (Radar summary maps). */
function requireRadarObject(result, field, source) {
  const value = result[field];
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error(`Cloudflare Radar ${source}: result.${field} is missing or not an object`);
  }
  return value;
}

async function fetchOutages() {
  const token = process.env.CLOUDFLARE_API_TOKEN;
  if (!token) {
    console.log('CLOUDFLARE_API_TOKEN not set — skipping');
    process.exit(0);

View on GitHub (pinned to 7d06c8633d)