vectordotdev/vector · error

invalid breaking fragment

Error message

invalid breaking fragment '{filename}': anchor must be a lowercase kebab-case slug (a-z, 0-9, hyphens); got '{anchor}'.

What it means

`vdev check changelog-fragments` rejects a breaking fragment whose optional `anchor` is not a lowercase kebab-case slug (only a-z, 0-9 and hyphens). The validation is done by `is_valid_anchor` and the error echoes the offending anchor text.

Solutions

  1. Rewrite the anchor as lowercase kebab-case, e.g. `my-change-1234`.
  2. Remove the anchor entirely if it is not needed (anchors are optional).
  3. Re-run `make check-changelog-fragments`.

Example fix

# before
anchor: My_Change
# after
anchor: my-change
Defensive patterns

Strategy: validation

Validate before calling

let anchor = "my-change";
assert!(anchor.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'), "anchor must be kebab-case");

Type guard

fn is_kebab(s: &str) -> bool { !s.is_empty() && s.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') }

Prevention

When it happens

Trigger: A breaking fragment file contains an anchor containing uppercase letters, spaces, underscores, or other characters outside `[a-z0-9-]`.

Common situations: Author writes `anchor: My_Change` or uses spaces instead of hyphens when giving the fragment a stable anchor for docs links.

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


AI-assisted analysis of vectordotdev/vector@bdb87aeaa4 (2026-09-16). Data as JSON: /api/errors/8a643fec6e12f739. Report an issue: GitHub.

Appendix: source

Thrown at vdev/src/commands/check/changelog_fragments.rs:277

    let body_before_authors = content
        .rsplit_once("\nauthors: ")
        .map_or(content, |(before, _)| before);

    let sections = parse_breaking_sections(body_before_authors)
        .map_err(|e| anyhow::anyhow!("invalid breaking fragment '{filename}': {e}"))?;

    if sections.title.is_empty() {
        bail!("invalid breaking fragment '{filename}': title must not be empty.");
    }
    if sections.title.contains("TODO") {
        bail!(
            "invalid breaking fragment '{filename}': title still contains the scaffolder placeholder 'TODO'."
        );
    }
    if let Some(anchor) = sections.anchor.as_deref()
        && !is_valid_anchor(anchor)
    {
        bail!(
            "invalid breaking fragment '{filename}': anchor must be a lowercase kebab-case slug (a-z, 0-9, hyphens); got '{anchor}'."
        );
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use indoc::{formatdoc, indoc};

    /// Build a full breaking-fragment file body from the given title line and section content.
    fn wrap(title_line: &str, summary: &str, migration: &str) -> String {
        formatdoc! {"
            {title_line}

            ## Summary

View on GitHub (pinned to bdb87aeaa4)