vectordotdev/vector · error
duplicate upgrade-guide anchor '#
Error message
duplicate upgrade-guide anchor '#{anchor}' shared by breaking fragments '{other}' and '{name}'. Override one with an explicit `{{#unique-slug}}`. What it means
The changelog check found two breaking-change fragments whose upgrade-guide headings derive the same Markdown anchor slug. Ambiguous anchors break deep links in the rendered upgrade guide. Fix by renaming one heading or adding an explicit `{{#unique-slug}}` override after the title.
Solutions
- Rename the heading in one of the two conflicting breaking fragments so the derived anchors differ.
- Add an explicit anchor override `{{#some-unique-slug}}` after the title of one fragment's heading.
- If the duplicate came from a merge/rebase, consolidate the two fragments into a single breaking fragment.
Example fix
// before: two fragments both titled
## Remove deprecated config option
// after
## Remove deprecated config option {{#remove-deprecated-config-option-sinks}} Defensive patterns
Strategy: validation
Validate before calling
let anchors: Vec<String> = std::fs::read_dir("changelog.d")?.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |x| x == "md"))
.filter_map(|e| std::fs::read_to_string(e.path()).ok())
.filter_map(|c| c.lines().find(|l| l.starts_with("## ")).map(|h| slugify(h)))
.collect();
let dupes: Vec<_> = anchors.iter().duplicates().collect();
assert!(dupes.is_empty(), "duplicate upgrade-guide anchors: {dupes:?}"); Prevention
- Give each breaking fragment a distinctive, feature-specific heading.
- Add an explicit `{{#slug}}` override whenever the title may be duplicated.
- Run `vdev check changelog-fragments` locally before pushing.
When it happens
Trigger: Running `vdev check changelog-fragments` (via exec) when two `*.breaking.md` fragments contain headings that normalize to the identical kebab-case anchor, and neither has an explicit `{{#slug}}` override.
Common situations: Two contributors independently add breaking-change fragments with identical titles like 'Remove deprecated option' before merging; rebasing multiple PRs each adding the same section title into the same release's changelog.d directory.
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
- invalid breaking fragment
- invalid fragment contents for
- invalid fragment contents for
- invalid fragment contents for
- invalid fragment contents for
AI-assisted analysis of vectordotdev/vector@bdb87aeaa4 (2026-09-16).
Data as JSON: /api/errors/8f9a2f47f80a15d7.
Report an issue: GitHub.
Appendix: source
Thrown at vdev/src/commands/check/changelog_fragments.rs:137
continue;
};
if !entry.breaking {
continue;
}
let content = std::fs::read_to_string(&path)?;
let body_before_authors = content
.rsplit_once("\nauthors: ")
.map_or(content.as_str(), |(before, _)| before);
let sections = parse_breaking_sections(body_before_authors)
.map_err(|e| anyhow::anyhow!("invalid breaking fragment '{name}': {e}"))?;
let anchor = sections.anchor.unwrap_or_else(|| slugify(§ions.title));
if !is_valid_anchor(&anchor) {
bail!(
"invalid breaking fragment '{name}': derived anchor '{anchor}' is not a valid kebab-case slug (add an explicit `{{#some-slug}}` after the title)."
);
}
if let Some(other) = seen.insert(anchor.clone(), name.clone()) {
bail!(
"duplicate upgrade-guide anchor '#{anchor}' shared by breaking fragments '{other}' and '{name}'. Override one with an explicit `{{#unique-slug}}`."
);
}
}
Ok(())
}
/// `git diff --name-only --diff-filter=<filter> --merge-base <merge_base> changelog.d`
///
/// `filter` is a `git diff` `--diff-filter` value: `A` for added-only, `M` for
/// modified-only, `AM` for both, etc.
fn diff_fragments(merge_base: &str, filter: &str) -> Result<Vec<PathBuf>> {
let filter_arg = format!("--diff-filter={filter}");
let out = git::run_and_check_output(&[
"diff",
"--name-only",
&filter_arg,
"--merge-base",View on GitHub (pinned to bdb87aeaa4)