koala73/worldmonitor · error · Error
Report slug/id must match ${SLUG_PATTERN}: ${report.slug} /
Error message
Report slug/id must match ${SLUG_PATTERN}: ${report.slug} / ${report.id} What it means
The research-reports builder validates each report's slug and id against /^[a-z0-9][a-z0-9-]*$/ before rendering pages, since both become URL path segments and file names. A slug/id that is empty, has uppercase, underscores, spaces, or starts with a hyphen throws this error naming the offending values.
Solutions
- Rewrite report.slug to lowercase alphanumeric words separated by hyphens, not starting with a hyphen.
- Apply the same pattern to report.id.
- Add a slugify helper at data-entry time so new reports are normalized automatically.
- Re-run the reports build to confirm validation passes.
Example fix
// before
{ id: 'Chokepoint_01', slug: 'Strait of Hormuz' }
// after
{ id: 'chokepoint-01', slug: 'strait-of-hormuz' } Defensive patterns
Strategy: validation
Validate before calling
const SLUG = /^[a-z0-9][a-z0-9-]*$/;
if (!SLUG.test(report.slug) || !SLUG.test(report.id)) throw new Error('report slug/id must match ' + SLUG); Prevention
- Slugify titles automatically at authoring time.
- Enforce the slug pattern in report schema/front-matter lint.
- Never use spaces, underscores, or uppercase in slugs/ids.
When it happens
Trigger: Adding or editing a research report entry whose slug or id violates the pattern — e.g. slug 'Q1_2026_Report', an id with uppercase, or an empty string from missing front-matter — then running the reports build.
Common situations: Hand-authored report metadata with human-friendly titles pasted into slug fields; renamed reports using underscores; automated imports that don't slugify; empty ids from template scaffolds.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Comparison measurement requires the captured snapshot date
- renderCountryDevelopments requires a country name
- Report ${report.id}: ${role} ${id} has no valid route in the
- ${label} HTTP 400
- Could not resolve ${JSON.stringify(echoCountryInput(raw))} t
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/54b7b30cf3272126.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/build-research-reports.mjs:710
function trackedLink(href, text, target, escapeHtml, extraAttrs = '') {
return `<a href="${escapeHtml(href)}" data-umami-event="research-cta" data-umami-event-target="${escapeHtml(target)}"${extraAttrs}>${text}</a>`;
}
export function renderResearchReportPage({
report,
snapshot,
metrics,
tpl,
baseUrl,
lastmod,
chokepointSlugById,
dataCatalog,
includedInDataCatalog,
}) {
const { escapeHtml, absoluteUrl, breadcrumbLd, withUtmSource, pageDocument } = tpl;
const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
if (!SLUG_PATTERN.test(report.slug) || !SLUG_PATTERN.test(report.id)) {
throw new Error(`Report slug/id must match ${SLUG_PATTERN}: ${report.slug} / ${report.id}`);
}
for (const [role, ids] of [
['focusChokepointId', [report.focusChokepointId]],
['contextChokepointId', report.contextChokepointIds],
]) {
for (const id of ids) {
if (!SLUG_PATTERN.test(chokepointSlugById.get(id) ?? '')) {
throw new Error(`Report ${report.id}: ${role} ${id} has no valid route in the chokepoint registry`);
}
}
}
const chokepointSlug = chokepointSlugById.get(report.focusChokepointId);
const path = `/research/${report.slug}/`;
const canonical = absoluteUrl(baseUrl, path);
const focus = snapshot.chokepoints[report.focusChokepointId];
const resolve = (text) => resolveMetricTokens(escapeHtml(text), metrics, escapeHtml);
const m = (id) => {
const metric = metrics.get(id);View on GitHub (pinned to 7d06c8633d)