tiangolo/fastapi · error · RuntimeError
{release_notes_file} must start with {latest_header!r}
Error message
{release_notes_file} must start with {latest_header!r} What it means
Raised by update_release_notes() in scripts/prepare_release.py:74. After confirming the release-notes header (error 113) and absence of the target version (error 114), the function requires the '## Latest Changes' heading to immediately follow the header (latest_header at scripts/prepare_release.py:72). New version sections are spliced in right after '## Latest Changes', so its presence and position are mandatory.
Source
Thrown at scripts/prepare_release.py:74
raise RuntimeError(
f"New version {version} must be greater than current version {current_version}"
)
return VERSION_PATTERN.sub(f'__version__ = "{version}"', content, count=1)
def update_release_notes(
content: str, version: str, release_date: date, release_notes_file: Path
) -> str:
if not content.startswith(RELEASE_NOTES_HEADER):
raise RuntimeError(
f"{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}"
)
if re.search(rf"^## {re.escape(version)}(?: \([^)]+\))?$", content, re.M):
raise RuntimeError(f"Release notes already contain a section for {version}")
latest_header = f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n"
if not content.startswith(latest_header):
raise RuntimeError(f"{release_notes_file} must start with {latest_header!r}")
release_header = f"## {version} ({release_date.isoformat()})"
return content.replace(
latest_header,
f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n\n{release_header}\n",
1,
)
def get_release_notes_body(content: str, version: str, release_notes_file: Path) -> str:
version_heading = re.compile(rf"(?m)^## {re.escape(version)}(?: \([^)]+\))?$")
match = version_heading.search(content)
if not match:
raise RuntimeError(
f"Could not find release notes section for {version} in {release_notes_file}"
)
next_match = VERSION_HEADING_PATTERN.search(content, match.end())View on GitHub (pinned to 3e8d1526d8)
Solutions
- Open the release notes file and ensure '## Latest Changes' appears immediately after the '# Release Notes' header.
- If prior version sections sit above it, move '## Latest Changes' back to the top (just under the H1).
- Re-run prepare.
Example fix
<!-- before --> # Release Notes ## 1.0.0 (2026-01-01) - ... ## Latest Changes <!-- after --> # Release Notes ## Latest Changes ## 1.0.0 (2026-01-01) - ...
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
RELEASE_NOTES_HEADER = "---\nhide:\n - navigation\n---\n\n# Release Notes\n\n"
LATEST_CHANGES_HEADER = "## Latest Changes"
def latest_header_ok(path: Path) -> bool:
return path.read_text(encoding="utf-8").startswith(
RELEASE_NOTES_HEADER + LATEST_CHANGES_HEADER + "\n"
) Try / catch
try:
updated = update_release_notes(content, version, d, release_notes_file)
except RuntimeError as e:
if "Latest Changes" in str(e):
raise SystemExit(f"Restore '## Latest Changes' under the header: {e}") from e
raise Prevention
- Always keep '## Latest Changes' directly under the '# Release Notes' header.
- After splicing a version section, ensure '## Latest Changes' remains above released sections.
- CI: assert the file starts with header + '## Latest Changes\n'.
When it happens
Trigger: Calling prepare when the release-notes file is missing the '## Latest Changes' heading, or it has been moved below existing version sections, or its text was altered.
Common situations: A previous release moved '## Latest Changes' but did not restore it. Someone renamed the heading. The file was hand-edited to put a version section above '## Latest Changes'.
Related errors
- {release_notes_file} must start with {RELEASE_NOTES_HEADER!r
- Code block (lines {start_line}-{end_line_no}) has different
- Number of code blocks does not match the number in the origi
- Couldn't auto-generate sponsors section
- Couldn't find pre section (<style>) in index.md
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/5bf0d8a4b1e66e24.
Report an issue: GitHub.