vectordotdev/vector · error
first line must be an H1 title (`# ...`)
Error message
first line must be an H1 title (`# ...`)
What it means
Parse error from parse_breaking_sections: the breaking-change fragment body must begin with an H1 markdown title ('# ...'), and this bail fires when the first section marker extracted from the (CRLF-normalized, padded) body is not an H1 heading — i.e. the file starts with body text, a smaller heading, or omits the title entirely. The H1 becomes the release-note entry title.
Solutions
- Add '# <title>' as the first line of the breaking fragment
- Move any intro text below the H1 title
- Ensure no leading blank lines or front matter precede the H1
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at vdev/src/commands/changelog/mod.rs:124 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of vectordotdev/vector@bdb87aeaa4 (2026-09-16).
Data as JSON: /api/errors/dd25df201a78f37d.
Report an issue: GitHub.
Appendix: source
Thrown at vdev/src/commands/changelog/mod.rs:124
/// Parse a breaking-fragment body (H1 title + `## Summary` + `## Migration`) via plain
/// string splits. Only the H1 title, the `## Summary` header, and the `## Migration` header
/// are mandatory; content inside each section is returned verbatim (any sub-headings the
/// author wrote — `### Old`, `### New`, whatever — pass through untouched).
pub(crate) fn parse_breaking_sections(body: &str) -> Result<BreakingSections> {
// Normalize CRLF → LF so Windows checkouts parse identically. Also pad the input so
// section markers like `\n## Migration\n` still match when the last section is empty
// and the file ends right after the header line.
let mut normalized = body.replace("\r\n", "\n");
if !normalized.ends_with('\n') {
normalized.push('\n');
}
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`");View on GitHub (pinned to bdb87aeaa4)