{"record":{"id":"45159a16068fb438","repo":"fastapi/fastapi","slug":"release-notes-file-must-start-with-release-note","errorCode":null,"errorMessage":"{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}","messagePattern":"(.+?) must start with (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"scripts/prepare_release.py","lineNumber":66,"sourceCode":"    if bump == \"minor\":\n        return f\"{major}.{minor + 1}.0\"\n    return f\"{major}.{minor}.{patch + 1}\"\n\n\ndef update_version_file(content: str, version: str, version_file: Path) -> str:\n    current_version = get_current_version(content, version_file)\n    if parse_version(version) <= parse_version(current_version):\n        raise RuntimeError(\n            f\"New version {version} must be greater than current version {current_version}\"\n        )\n    return VERSION_PATTERN.sub(f'__version__ = \"{version}\"', content, count=1)\n\n\ndef update_release_notes(\n    content: str, version: str, release_date: date, release_notes_file: Path\n) -> str:\n    if not content.startswith(RELEASE_NOTES_HEADER):\n        raise RuntimeError(\n            f\"{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}\"\n        )\n    if re.search(rf\"^## {re.escape(version)}(?: \\([^)]+\\))?$\", content, re.M):\n        raise RuntimeError(f\"Release notes already contain a section for {version}\")\n\n    latest_header = f\"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\\n\"\n    if not content.startswith(latest_header):\n        raise RuntimeError(f\"{release_notes_file} must start with {latest_header!r}\")\n\n    release_header = f\"## {version} ({release_date.isoformat()})\"\n    return content.replace(\n        latest_header,\n        f\"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\\n\\n{release_header}\\n\",\n        1,\n    )\n\n\ndef get_release_notes_body(content: str, version: str, release_notes_file: Path) -> str:","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/fastapi/fastapi/blob/a1fa70d4237d50aae6586a0d9b229df583463d21/scripts/prepare_release.py#L48-L84","documentation":"Format check in scripts/prepare_release.py update_release_notes (:60-67): the release-notes markdown file must begin with the exact two headers '# Release Notes' followed immediately by '## Latest Changes' (RELEASE_NOTES_HEADER + LATEST_CHANGES_HEADER + newline). The first check (:61-63) fails if the file does not start with '# Release Notes' at all; the new release section is spliced right after '## Latest Changes', so a reordered/missing header breaks the splice and the script refuses to edit the file.","triggerScenarios":"Running release preparation when docs/en/docs/release-notes.md was edited to remove or reorder the leading headers, when a stray line (badge, blank intro, HTML comment) was added above '# Release Notes', or when the wrong file was passed as release_notes_file.","commonSituations":"Contributors prepending banners or acknowledgements to the top of release-notes.md; tools that regenerate the file without the header; checking out a branch where the header was refactored.","solutions":["Make docs/en/docs/release-notes.md start with exactly '# Release Notes\\n\\n## Latest Changes\\n' (then the existing changelog body).","Remove any content above '# Release Notes' (move badges/notes into the body).","Restore via git: git checkout HEAD -- docs/en/docs/release-notes.md if the edit was accidental (after saving any wanted entries).","Re-run the release command once the header block matches."],"exampleFix":"# docs/en/docs/release-notes.md (before)\n<!-- banner -->\n# Release Notes\n## Latest Changes\n\n# after\n# Release Notes\n\n## Latest Changes\n","handlingStrategy":"validation","validationCode":"EXPECTED_HEAD = \"# Release Notes\\n\\n## Latest Changes\\n\"\n\ndef release_notes_wellformed(text: str) -> bool:\n    return text.startswith(\"# Release Notes\") and text.startswith(EXPECTED_HEAD)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never add content above '# Release Notes' in release-notes.md; new entries go under '## Latest Changes'.","Protect the file's header block with a CI lint that asserts the startswith(EXPECTED_HEAD) invariant.","Restore accidentally edited headers from git before re-running release prep."],"tags":["fastapi","release","changelog","scripts","formatting"],"backgroundTag":null,"analyzedSha":"a1fa70d4237d50aae6586a0d9b229df583463d21","analyzedAt":"2026-08-14T19:41:58.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}