vectordotdev/vector · error

exactly one `## Migration` section is required

Error message

exactly one `## Migration` section is required

What it means

`parse_breaking_sections` requires a breaking fragment to contain exactly one `## Migration` section. If the text after the H1 title contains zero or multiple occurrences of MIGRATION_MARKER, it bails with this error. The Migration section is mandatory so every breaking change ships with upgrade instructions.

Solutions

  1. Add exactly one `## Migration` section after `## Summary`.
  2. Merge duplicated `## Migration` sections into one.
  3. Use the `vdev changelog new` template so both required sections exist.

Example fix

// before
# My change
## Summary
Something broke.
// after
# My change
## Summary
Something broke.
## Migration
Use the new option instead.
Defensive patterns

Strategy: validation

Validate before calling

let migration_count = text.matches("## Migration").count();
if migration_count != 1 {
    eprintln!("fragment must contain exactly one '## Migration' (found {migration_count})");
}

Try / catch

if let Err(e) = parse_breaking_sections(text) {
    eprintln!("fix fragment structure: {e}");
}

Prevention

When it happens

Trigger: Parsing a breaking fragment that omits `## Migration`, uses a different casing/heading level, or contains the `## Migration` heading more than once.

Common situations: Deleting the template's Migration section because "nothing to migrate"; duplicating the heading while reorganizing content; migrating legacy fragments that lacked the section.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at vdev/src/commands/changelog/mod.rs:137

    let body: &str = &normalized;

    // 1) File must begin with an H1 title on the first line — no leading blank lines.
    let title_body = body
        .strip_prefix("# ")
        .ok_or_else(|| anyhow::anyhow!("first line must be an H1 title (`# ...`)"))?;
    let (title_line, after_title) = title_body.split_once('\n').unwrap_or((title_body, ""));
    let (title_text, anchor) = split_title_and_anchor(title_line);
    let title = title_text.to_string();
    if title.is_empty() {
        bail!("H1 title must not be empty");
    }

    // 2) Exactly one `## Summary` and one `## Migration`, in that order.
    if after_title.matches(SUMMARY_MARKER).count() != 1 {
        bail!("exactly one `## Summary` section is required");
    }
    if after_title.matches(MIGRATION_MARKER).count() != 1 {
        bail!("exactly one `## Migration` section is required");
    }
    let s_pos = after_title.find(SUMMARY_MARKER).unwrap();
    let m_pos = after_title.find(MIGRATION_MARKER).unwrap();
    if s_pos >= m_pos {
        bail!("`## Summary` must come before `## Migration`");
    }

    // Reject any prose between the H1 title and `## Summary`. Someone hand-migrating an
    // old free-form breaking fragment could carry over the previous body here and it
    // would silently disappear from both the release CUE and the upgrade guide.
    // Everything before Summary must be whitespace (`\n## Summary\n` starts with the
    // leading newline, so `s_pos` includes it).
    if let Some(prefix) = after_title.get(..s_pos)
        && !prefix.trim().is_empty()
    {
        bail!(
            "content between the title and `## Summary` is not allowed — move it into `## Summary` or `## Migration`."
        );

View on GitHub (pinned to bdb87aeaa4)