koala73/worldmonitor · error · Error

ECCC_INVALID_ID

ECCC_INVALID_ID

Error message

ECCC_INVALID_ID

What it means

ECCC_INVALID_ID is thrown while paginating ECCC weather-alert GeoJSON: every feature in a fetched page must carry a non-empty string `id`. The script throws immediately when a feature's id is missing, not a string, or blank after trim, because downstream dedup and status matching key off feature ids. It is a strict data-integrity guard against malformed upstream payloads.

Solutions

  1. Inspect the offending page payload and confirm each feature has a non-empty string `id`; if ECCC changed its schema, update the parsing/validation to the new id field
  2. Check which status (issued vs continued) and page index failed by logging the URL and page before the throw, then replay that request to confirm it is reproducible upstream rather than a transient payload
  3. Add a pre-loop validation or per-feature fallback (e.g. derive id from properties) only if ECCC legitimately emits id-less features, and document the deviation
  4. If caused by a stub/fixture in tests, fix the fixture so every feature has a string id

Example fix

// before
for (const feature of page) {
  if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');
  ...
}
// after
for (const feature of page) {
  if (typeof feature?.id !== 'string' || !feature.id.trim()) {
    console.error('ECCC feature missing id', JSON.stringify(feature).slice(0, 200));
    throw new Error('ECCC_INVALID_ID');
  }
  ...
}
Defensive patterns

Strategy: validation

Validate before calling

function hasValidIds(features) {
  return Array.isArray(features) && features.every((f) => typeof f?.id === 'string' && f.id.trim().length > 0);
}
if (!hasValidIds(page)) throw new Error('ECCC_INVALID_ID');

Type guard

const isFeatureWithId = (f) => typeof f?.id === 'string' && f.id.trim().length > 0;

Try / catch

try {
  await fetchEcccPages();
} catch (err) {
  if (err.message === 'ECCC_INVALID_ID') {
    console.error('Upstream ECCC payload contained a feature without a valid id; skipping cycle');
  }
}

Prevention

When it happens

Trigger: A page from the ECCC alerts API (issued or continued status fetch) contains a feature where `feature.id` is undefined/null, a non-string (e.g. number), or a whitespace-only string; iteration happens inside the do/while loop collecting statusFeatures until numberMatched is reached.

Common situations: ECCC changes or drops the `id` property in a GeoJSON response schema; a proxy or cache returns a partially transformed payload; a test stub fixture omits ids; an unexpected HTML/error page parsed leniently yields feature-like objects without ids.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at scripts/_weather-alert-select.mjs:784

        pages += 1;
        const data = await fetchApprovedWeatherJson(url.toString(), {
          allowedHosts: [ECCC_HOST], maxBytes, fetchFn, userAgent, byteBudget,
        });
        const page = requireAlertFeatures(data);
        if (data.type !== 'FeatureCollection'
          || !Number.isSafeInteger(data.numberMatched) || data.numberMatched < 0
          || !Number.isSafeInteger(data.numberReturned) || data.numberReturned !== page.length
          || page.length > ECCC_PAGE_SIZE) {
          throw new Error('ECCC_MALFORMED_PAGE');
        }
        if (matched !== undefined && data.numberMatched !== matched) throw new Error('ECCC_COUNT_DRIFT');
        matched = data.numberMatched;
        if (statusFeatures.length + page.length > matched
          || (page.length === 0 && statusFeatures.length < matched)) {
          throw new Error('ECCC_PAGE_PROGRESS');
        }
        for (const feature of page) {
          if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');
          if (seenIds.has(feature.id)) throw new Error('ECCC_DUPLICATE_ID');
          seenIds.add(feature.id);
        }
        statusFeatures.push(...page);
      } while (statusFeatures.length < matched);
      features.push(...statusFeatures);
    } catch (err) {
      failures.push(err);
      failedStatuses.push(status);
    }
  }
  if (failures.length === ECCC_LIVE_STATUSES.length) {
    const detail = failures.map((err) => err?.message || String(err)).join('; ');
    throw new Error(`ECCC issued and continued fetches both failed: ${detail}`);
  }
  // Returns an OBJECT, not a bare array, so a partial fetch cannot be consumed
  // as if it were the whole set. `issued` and `continued` are separate collections
  // and each carries alerts the other does not: `continued` is where an ONGOING

View on GitHub (pinned to 7d06c8633d)