{"record":{"id":"dd25df201a78f37d","repo":"vectordotdev/vector","slug":"first-line-must-be-an-h1-title","errorCode":null,"errorMessage":"first line must be an H1 title (`# ...`)","messagePattern":"first line must be an H1 title \\(`# \\.\\.\\.`\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"vdev/src/commands/changelog/mod.rs","lineNumber":124,"sourceCode":"\n/// Parse a breaking-fragment body (H1 title + `## Summary` + `## Migration`) via plain\n/// string splits. Only the H1 title, the `## Summary` header, and the `## Migration` header\n/// are mandatory; content inside each section is returned verbatim (any sub-headings the\n/// author wrote — `### Old`, `### New`, whatever — pass through untouched).\npub(crate) fn parse_breaking_sections(body: &str) -> Result<BreakingSections> {\n    // Normalize CRLF → LF so Windows checkouts parse identically. Also pad the input so\n    // section markers like `\\n## Migration\\n` still match when the last section is empty\n    // and the file ends right after the header line.\n    let mut normalized = body.replace(\"\\r\\n\", \"\\n\");\n    if !normalized.ends_with('\\n') {\n        normalized.push('\\n');\n    }\n    let body: &str = &normalized;\n\n    // 1) File must begin with an H1 title on the first line — no leading blank lines.\n    let title_body = body\n        .strip_prefix(\"# \")\n        .ok_or_else(|| anyhow::anyhow!(\"first line must be an H1 title (`# ...`)\"))?;\n    let (title_line, after_title) = title_body.split_once('\\n').unwrap_or((title_body, \"\"));\n    let (title_text, anchor) = split_title_and_anchor(title_line);\n    let title = title_text.to_string();\n    if title.is_empty() {\n        bail!(\"H1 title must not be empty\");\n    }\n\n    // 2) Exactly one `## Summary` and one `## Migration`, in that order.\n    if after_title.matches(SUMMARY_MARKER).count() != 1 {\n        bail!(\"exactly one `## Summary` section is required\");\n    }\n    if after_title.matches(MIGRATION_MARKER).count() != 1 {\n        bail!(\"exactly one `## Migration` section is required\");\n    }\n    let s_pos = after_title.find(SUMMARY_MARKER).unwrap();\n    let m_pos = after_title.find(MIGRATION_MARKER).unwrap();\n    if s_pos >= m_pos {\n        bail!(\"`## Summary` must come before `## Migration`\");","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/vdev/src/commands/changelog/mod.rs#L106-L142","documentation":"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.","triggerScenarios":"Thrown at vdev/src/commands/changelog/mod.rs:124 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}