{"record":{"id":"4cda5d668ead3d9d","repo":"vectordotdev/vector","slug":"invalid-breaking-fragment-name-derived-anchor-anchor-is-not","errorCode":null,"errorMessage":"invalid breaking fragment '{name}': derived anchor '{anchor}' is not a valid kebab-case slug (add an explicit `{{#some-slug}}` after the title).","messagePattern":"invalid breaking fragment '(.+?)': derived anchor '(.+?)' is not a valid kebab-case slug \\(add an explicit `(.+?)\\}` after the title\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"vdev/src/commands/check/changelog_fragments.rs","lineNumber":132,"sourceCode":"        if parts.len() != 3 || parts[2] != \"md\" {\n            continue;\n        }\n        let fragment_type = parts[1];\n        let Some(entry) = FRAGMENT_TYPES.iter().find(|t| t.name == fragment_type) else {\n            continue;\n        };\n        if !entry.breaking {\n            continue;\n        }\n        let content = std::fs::read_to_string(&path)?;\n        let body_before_authors = content\n            .rsplit_once(\"\\nauthors: \")\n            .map_or(content.as_str(), |(before, _)| before);\n        let sections = parse_breaking_sections(body_before_authors)\n            .map_err(|e| anyhow::anyhow!(\"invalid breaking fragment '{name}': {e}\"))?;\n        let anchor = sections.anchor.unwrap_or_else(|| slugify(&sections.title));\n        if !is_valid_anchor(&anchor) {\n            bail!(\n                \"invalid breaking fragment '{name}': derived anchor '{anchor}' is not a valid kebab-case slug (add an explicit `{{#some-slug}}` after the title).\"\n            );\n        }\n        if let Some(other) = seen.insert(anchor.clone(), name.clone()) {\n            bail!(\n                \"duplicate upgrade-guide anchor '#{anchor}' shared by breaking fragments '{other}' and '{name}'. Override one with an explicit `{{#unique-slug}}`.\"\n            );\n        }\n    }\n    Ok(())\n}\n\n/// `git diff --name-only --diff-filter=<filter> --merge-base <merge_base> changelog.d`\n///\n/// `filter` is a `git diff` `--diff-filter` value: `A` for added-only, `M` for\n/// modified-only, `AM` for both, etc.\nfn diff_fragments(merge_base: &str, filter: &str) -> Result<Vec<PathBuf>> {\n    let filter_arg = format!(\"--diff-filter={filter}\");","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/vdev/src/commands/check/changelog_fragments.rs#L114-L150","documentation":"Breaking-change fragments feed the upgrade guide, which anchors each section by a kebab-case slug. The checker parses the fragment's breaking sections, derives an anchor from the explicit `{{#anchor}}` or by slugifying the title, and bails when the derived anchor is not a valid kebab-case slug.","triggerScenarios":"A `breaking` fragment whose title slugifies to something invalid (empty after slugification, or containing characters outside kebab-case) and which lacks an explicit `{{#some-slug}}` after the title, parsed by validate_breaking_anchor_set during the check.","commonSituations":"Titles made entirely of punctuation/emoji (slugify yields '' or junk); titles with slashes/underscores; copy-pasted Markdown headings without adding an explicit anchor.","solutions":["Add an explicit anchor after the section title, e.g. `## Upgrade notes {{#my-upgrade-note}}`, then re-run the check","Or rewrite the title so it slugifies to valid kebab-case (lowercase letters, digits, hyphens)","Check the derived anchor with the checker output and confirm `is_valid_anchor` accepts it (^[a-z0-9]+(-[a-z0-9]+)*$ pattern)"],"exampleFix":"// before\n## Removes `--legacy-flag`!!!\n// after\n## Removes `--legacy-flag` {{#remove-legacy-flag}}","handlingStrategy":"validation","validationCode":"const anchor = explicitAnchor ?? slugify(title);\nif (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(anchor)) throw new Error(`add explicit {{#anchor}} for '${title}'`);","typeGuard":"const isValidKebab = (s) => /^[a-z0-9]+(-[a-z0-9]+)*$/.test(s);","tryCatchPattern":"try { runCheck(); } catch (e) { const m = /derived anchor '([^']+)'.*not a valid/.exec(e.message); if (m) addExplicitAnchorToFragment(m[1]); else throw e; }","preventionTips":["Add an explicit `{{#kebab-slug}}` to every breaking-change section title","Keep breaking-section titles simple ASCII so slugification is predictable","Run the fragments check locally before pushing breaking-change PRs"],"tags":["markdown","validation","changelog"],"backgroundTag":"invalid-identifier-format","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}