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
- 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.
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
- 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.
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
- Cloudflare Radar ${source}: result.${field} is missing or no
- Redis transaction failed: ${results?.error || 'invalid respo
- DNS ${recordType} lookup failed: HTTP ${response.status}
- Invalid scorecard bloc selection.
- ECCC_PAGE_LIMIT
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)