{"record":{"id":"576e1741701ee999","repo":"koala73/worldmonitor","slug":"eccc-issued-and-continued-fetches-both-failed-detail","errorCode":null,"errorMessage":"ECCC issued and continued fetches both failed: ${detail}","messagePattern":"ECCC issued and continued fetches both failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/_weather-alert-select.mjs","lineNumber":798,"sourceCode":"          || (page.length === 0 && statusFeatures.length < matched)) {\n          throw new Error('ECCC_PAGE_PROGRESS');\n        }\n        for (const feature of page) {\n          if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');\n          if (seenIds.has(feature.id)) throw new Error('ECCC_DUPLICATE_ID');\n          seenIds.add(feature.id);\n        }\n        statusFeatures.push(...page);\n      } while (statusFeatures.length < matched);\n      features.push(...statusFeatures);\n    } catch (err) {\n      failures.push(err);\n      failedStatuses.push(status);\n    }\n  }\n  if (failures.length === ECCC_LIVE_STATUSES.length) {\n    const detail = failures.map((err) => err?.message || String(err)).join('; ');\n    throw new Error(`ECCC issued and continued fetches both failed: ${detail}`);\n  }\n  // Returns an OBJECT, not a bare array, so a partial fetch cannot be consumed\n  // as if it were the whole set. `issued` and `continued` are separate collections\n  // and each carries alerts the other does not: `continued` is where an ONGOING\n  // warning lives after its first issue. Returning just the surviving features\n  // when one status 500s publishes a silently truncated national alert set —\n  // and on the relay, whose purge semantics always overwrite, it DELETES every\n  // continued alert from the live key while health still reads OK.\n  return {\n    features,\n    failedStatuses,\n    partial: failedStatuses.length > 0,\n    failureDetail: failures.map((err) => err?.message || String(err)).join('; '),\n  };\n}\n\n/**\n * Host-policy fetch: allowlist, reject redirects, timeout, byte ceiling.","sourceCodeStart":780,"sourceCodeEnd":816,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/_weather-alert-select.mjs#L780-L816","documentation":"This error aggregates all failures from fetching both ECCC alert statuses (issued and continued). The script attempts each live status, collecting errors; if EVERY status fetch fails, it throws with all underlying messages joined so the caller sees why neither collection could be built. It exists to prevent publishing a silently truncated national alert set from partial data.","triggerScenarios":"Both the issued-status and continued-status ECCC fetches throw (network failure, non-OK HTTP, invalid payloads, timeouts, byte-budget limits); `failures.length === ECCC_LIVE_STATUSES.length` triggers the aggregate throw with each error's message.","commonSituations":"ECCC API outage or maintenance window; corporate proxy/firewall blocking the endpoint; expired upstream TLS or DNS failure in CI; rate limiting or 5xx from the weather service; misconfigured base URL in environment.","solutions":["Read the joined `detail` messages to identify the root cause per status (network vs HTTP status vs payload), then address that specific failure first","Check ECCC service status / retry after a short backoff if the cause is a transient outage or 5xx","Verify network egress, proxy settings, and DNS in the environment running the script (CI runners often lack outbound access)","Confirm the ECCC endpoint base URL and User-Agent configuration have not drifted from current API requirements"],"exampleFix":"// before\nthrow new Error(`ECCC issued and continued fetches both failed: ${detail}`);\n// after\ntry {\n  return await fetchEcccStatuses();\n} catch (err) {\n  console.error(err.message);\n  return { issued: [], continued: [], degraded: true };\n}","handlingStrategy":"fallback","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  const statuses = await fetchEcccIssuedAndContinued();\n} catch (err) {\n  if (String(err.message).startsWith('ECCC issued and continued fetches both failed')) {\n    // keep last-known-good alert set; do not publish an empty/truncated set\n    useStaleAlertSnapshot();\n  }\n}","preventionTips":["Keep the previous successful alert snapshot and serve it with a staleness flag on total failure","Schedule fetches with jitter/backoff so transient outages self-heal","Alert/monitor when this aggregate error fires — it means the national alert feed is unavailable","Keep the underlying per-status errors (the joined detail) in logs for triage"],"tags":["network","weather-api","aggregated-failure","outage"],"backgroundTag":"upstream-api-error","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"}