vectordotdev/vector · error
duplicate upgrade-guide anchor '#
Error message
duplicate upgrade-guide anchor '#{}' shared by breaking fragments '{other}' and '{}'. Override one with `{{#unique-slug}}`. What it means
During breaking-fragment validation, anchors must be unique across the release's upgrade guide; `validate_breaking_anchors` keeps a HashMap of anchor-to-title and bails when two fragments map to the same anchor. Duplicate anchors would produce ambiguous markdown links in the generated upgrade guide.
Solutions
- Identify the two conflicting fragment titles from the message.
- Override one fragment's anchor with a unique slug, e.g. `{{#unique-slug}}` or a more descriptive name.
- Run `vdev check changelog-fragments` to confirm anchors are valid and unique.
- Re-run `vdev release generate-cue`.
Example fix
// before: two fragments both use {{#configuration}}
## new required config field {{#configuration}}
// after
## new required config field {{#new-required-config-field}} Defensive patterns
Strategy: validation
Validate before calling
// detect duplicate anchors across changelog.d before release
const anchors = fragments.map(f => f.anchor);
const dupes = anchors.filter((a, i) => anchors.indexOf(a) !== i);
if (dupes.length) throw new Error(`duplicate anchors: ${dupes.join(", ")}`); Try / catch
if let Err(e) = run() {
if e.to_string().contains("duplicate upgrade-guide anchor") {
eprintln!("rename one fragment's {{#slug}} to a unique value and re-run");
}
} Prevention
- Derive anchors from the fragment filename/PR number to keep them unique.
- Grep changelog.d for the intended slug before reusing it.
- Include a duplicate-anchor check in CI alongside the fragment lint.
When it happens
Trigger: Two or more breaking changelog fragments in changelog.d declare the same `{{#slug}}` anchor (or derive identical slugs from their titles), and `vdev release generate-cue` runs validate_breaking_anchors.
Common situations: Two PRs independently picked the same intuitive slug like `{{#breaking-change}}` or `{{#configuration}}`, or a fragment was copied from an earlier one without renaming its anchor.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- breaking fragment ' ' has an invalid anchor ' '. Add ` }`…
- content between the title and `## Summary` is not allowed —…
- Duplicate deprecation fragments for `what
- exactly one `## Migration` section is required
- exactly one `## Summary` section is required
AI-assisted analysis of vectordotdev/vector@0d4ab78a4f (2026-09-16).
Data as JSON: /api/errors/0eb43e957bb32175.
Report an issue: GitHub.
Appendix: source
Thrown at vdev/src/commands/release/generate_cue.rs:751
let version_slug = format!("{}-{}-{}", version.major, version.minor, version.patch);
format!("{date}-{version_slug}-upgrade-guide.md")
}
/// Fail the release if any breaking fragment produced an invalid, empty, or duplicate
/// anchor. Uses the same anchor rules as `vdev check changelog-fragments` (shared through
/// `commands::changelog::is_valid_anchor`) so a fragment that passes CI can't fail here.
fn validate_breaking_anchors(breaking: &[&BreakingDetails]) -> Result<()> {
let mut seen = std::collections::HashMap::<&str, &str>::new();
for b in breaking {
if !crate::commands::changelog::is_valid_anchor(&b.anchor) {
bail!(
"breaking fragment '{}' has an invalid anchor '{}'. Add `{{#some-valid-slug}}` after the title.",
b.title,
b.anchor,
);
}
if let Some(other) = seen.insert(b.anchor.as_str(), b.title.as_str()) {
bail!(
"duplicate upgrade-guide anchor '#{}' shared by breaking fragments '{other}' and '{}'. Override one with `{{#unique-slug}}`.",
b.anchor,
b.title,
);
}
}
Ok(())
}
/// Write `content` to `path` via a `.tmp` sibling then atomic rename. Prevents leaving a
/// partial output behind if the process is killed mid-write.
fn atomic_write(path: &Path, content: &str) -> Result<()> {
let tmp = path.with_extension(format!(
"{}.tmp",
path.extension().and_then(|e| e.to_str()).unwrap_or("")
));
fs::write(&tmp, content).with_context(|| format!("Failed to write {}", tmp.display()))?;
fs::rename(&tmp, path)View on GitHub (pinned to 0d4ab78a4f)