koala73/worldmonitor · warning

Conflict seed unavailable

Error message

Conflict seed unavailable

What it means

buildSources' listConflicts seed reader calls the internal ACLED listAcledEvents handler and, if the response is flagged X-No-Cache (the handler's signal that upstream data could not be fetched and no cached fallback exists), it throws 'Conflict seed unavailable'. The map-frame embed aborts rather than rendering a stale or empty conflict layer.

Solutions

  1. Verify the ACLED upstream/API key is valid and within rate limits
  2. Warm the conflict cache (seed job) so the embed can serve cached data
  3. Retry the embed after the ACLED upstream recovers — the failure is per-request
  4. Optionally degrade the embed UI to hide the conflicts layer instead of failing the whole frame

Example fix

// before
const frame = await fetch('/api/embed/map-frame'); // 500 on conflict-seed failure
// after
const res = await fetch('/api/embed/map-frame');
if (!res.ok) renderFrameWithoutConflicts(await res.text().catch(() => ''));
Defensive patterns

Strategy: fallback

Validate before calling

// before rendering the embed, confirm the conflict feed is warm
const health = await fetch('/api/health'); // check the acled/conflict standalone health key first

Type guard

null

Try / catch

try { frame = await renderMapFrame(); } catch (e) { if (e.message === 'Conflict seed unavailable') return renderMapFrameWithoutConflicts(); throw e; }

Prevention

When it happens

Trigger: Rendering /api/embed/map-frame while the ACLED upstream fails or is rate-limited and no cached conflict data exists, causing listAcledEvents to respond with X-No-Cache. Only affects the conflicts layer; earthquakes/natural events have separate readers.

Common situations: ACLED API key missing or expired; ACLED rate limit hit on a busy embed; upstream outage during a cold cache; Redis cache eviction leaving no seed.

Related errors


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

Appendix: source

Thrown at api/embed/map-frame.ts:78

/** Build the generated handler context for an in-process source read. */
function handlerContext(req: Request) {
  return { request: req, pathParams: {}, headers: Object.fromEntries(req.headers) };
}

function buildSources(req: Request): EmbedMapFrameSources {
  const ctx = handlerContext(req);
  // Each upstream is called with ITS OWN defaults — zeros mean "handler
  // default" across these generated requests. Forwarding a caller-supplied
  // window or page size here would hand a stolen credential the knobs this
  // endpoint exists to remove.
  return {
    listConflicts: async () => {
      // Isolate this source's fallback signal from the other parallel readers.
      const conflictReq = new Request(req);
      const response = await listAcledEvents(handlerContext(conflictReq), { start: 0, end: 0, pageSize: 0, cursor: '', country: '' });
      if (drainResponseHeaders(conflictReq)?.['X-No-Cache']) {
        markNoCacheResponse(req);
        throw new Error('Conflict seed unavailable');
      }
      return response.events;
    },
    listEarthquakes: async () =>
      (await listEarthquakes(ctx, { minMagnitude: 0, start: 0, end: 0, pageSize: 0, cursor: '' })).earthquakes,
    listNaturalEvents: async () => (await listNaturalEvents(ctx, { days: 30 })).events,
    listProtests: async () =>
      (await listUnrestEvents(ctx, {
        country: '',
        start: 0,
        end: 0,
        pageSize: 0,
        cursor: '',
        // The handler reads neither the bbox nor minSeverity; these are here
        // only to satisfy the generated request type. Kept at their zero values
        // so that if it ever starts reading them, the embed asks for no filter
        // rather than silently inheriting one.
        minSeverity: 'SEVERITY_LEVEL_UNSPECIFIED',

View on GitHub (pinned to 7d06c8633d)