{"record":{"id":"69427b08a21cb20e","repo":"koala73/worldmonitor","slug":"http-resp-status-seed-internet-outages","errorCode":null,"errorMessage":"HTTP ${resp.status}","messagePattern":"HTTP \\$\\{resp\\.status\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-internet-outages.mjs","lineNumber":217,"sourceCode":" * logged, but must not withhold a confirmed protocol/vector summary. Keep this\n * distinction explicit: silently promoting the optional slice to required (or\n * demoting a required one) changes published coverage without changing counts.\n */\nasync function fetchDdosData(token) {\n  const headers = {\n    'User-Agent': CHROME_UA,\n    ...(token ? { Authorization: `Bearer ${token}` } : {}),\n  };\n\n  // Reports `degraded` rather than just returning [] so the caller can stamp the\n  // shortfall on seed-meta. An unconfirmed empty slice riding inside a CONFIRMED\n  // payload is the exact conflation this issue is about: without the marker, a\n  // permanently 4xx target endpoint would blank the DDoS map's target countries\n  // forever while recordCount (protocol+vector) never moves and health stays OK.\n  const fetchOptionalTargetLocations = async () => {\n    try {\n      const resp = await fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/top/locations/target?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) });\n      if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\n      const result = requireRadarResult(await resp.json(), 'DDoS target locations');\n      return {\n        items: requireRadarArray(result, 'top_0', 'DDoS target locations')\n          .filter((item) => item && typeof item === 'object' && !Array.isArray(item)),\n        degraded: false,\n      };\n    } catch (err) {\n      console.warn(`  CF Radar DDoS target locations unavailable (optional slice): ${err?.message || err}`);\n      return { items: [], degraded: true };\n    }\n  };\n\n  const [protocolResp, vectorResp, targetSlice] = await Promise.all([\n    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/protocol?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),\n    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/vector?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),\n    fetchOptionalTargetLocations(),\n  ]);\n","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-internet-outages.mjs#L199-L235","documentation":"fetchOptionalTargetLocations calls the Cloudflare Radar layer3 attacks/top/locations/target endpoint (7d range) and throws `HTTP ${resp.status}` when `resp.ok` is false, i.e. any status outside 200-299. This seed marks the DDoS target-locations feed as optional/degradable, but the throw still propagates unless caught upstream, so a persistent 4xx from this endpoint blanks the DDoS map's target countries while protocol/vector record counts never move and overall health stays OK. The error string carries only the numeric status, not the body, so diagnosis usually needs the status code alone.","triggerScenarios":"Cloudflare Radar returns a non-2xx status for GET /radar/attacks/layer3/top/locations/target?dateRange=7d — e.g. expired/invalid CF API token (401/403), plan-level access restriction on the target-locations route, transient 5xx, or a rate-limit 429 — detected by the `if (!resp.ok) throw` check after a 15s-capped fetch.","commonSituations":"Rotated or unscoped CLOUDFLARE_API_TOKEN lacking Radar read permission; free-tier account hitting routes behind a paid plan; Radar API incident/degradation returning 500s; hitting per-minute quota during repeated seed runs.","solutions":["Verify the Cloudflare API token used for `headers` is valid and has Radar read scope (curl the endpoint manually with the same headers).","Check the exact status in the message: 401/403 → fix token/permissions; 429 → back off and rerun; 5xx → retry later or during a Radar incident check status.cloudflare.com.","Since this feed is deliberately optional, wrap the `fetchOptionalTargetLocations()` call (in fetchDdosData's Promise.all) with `.catch(() => ({ items: [], degraded: true }))` so a broken target-locations route degrades instead of killing the seed.","Log `degraded: true` to seed-meta/health output so the blank DDoS target map is observable instead of silent."],"exampleFix":"// before\nfetchOptionalTargetLocations(),\n// after\nfetchOptionalTargetLocations().catch((err) => {\n  console.warn(`DDoS target locations degraded: ${err.message}`);\n  return { items: [], degraded: true };\n}),","handlingStrategy":"try-catch","validationCode":"// Best-effort pre-check before the seed run:\nconst resp = await fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/top/locations/target?dateRange=7d`, { headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}` } });\nif (!resp.ok) console.warn(`target-locations precheck failed: ${resp.status}; feed will degrade`);","typeGuard":null,"tryCatchPattern":"try {\n  const targets = await fetchOptionalTargetLocations();\n} catch (err) {\n  if (!/^HTTP \\d+$/.test(err.message)) throw err; // only swallow the expected HTTP-status error\n  logDegraded('ddos-target-locations', err.message);\n  const targets = { items: [], degraded: true };\n}","preventionTips":["Rotate and scope-check the Cloudflare token before scheduled runs; alert on 401/403 statuses.","Treat explicitly-optional feeds as degradable: catch and mark degraded instead of failing the seed.","Record per-feed degradation in seed-meta so a blank DDoS target map is visible in health output.","Monitor Cloudflare status/Radar incidents for 5xx clusters."],"tags":["http","cloudflare-radar","seed-script","network"],"backgroundTag":"http-error-response","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"}