tiangolo/fastapi · error · RuntimeError

{release_notes_file} must start with {RELEASE_NOTES_HEADER!r

Error message

{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}

What it means

Raised by update_release_notes() in scripts/prepare_release.py:66 when the release-notes Markdown file does not start with the exact RELEASE_NOTES_HEADER (front matter hiding navigation, a blank line, '# Release Notes', blank line — scripts/prepare_release.py:12-19). This header is the anchor used to splice in new version sections, so a missing or altered header would misplace new entries.

Source

Thrown at scripts/prepare_release.py:66

    if bump == "minor":
        return f"{major}.{minor + 1}.0"
    return f"{major}.{minor}.{patch + 1}"


def update_version_file(content: str, version: str, version_file: Path) -> str:
    current_version = get_current_version(content, version_file)
    if parse_version(version) <= parse_version(current_version):
        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:

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Open the release notes file and restore the exact header block: '---\nhide:\n - navigation\n---\n\n# Release Notes\n\n'.
  2. Restore from git history if unsure of the exact bytes.
  3. Re-run prepare.

Example fix

<!-- before -->
# Release Notes
<!-- after -->
---
hide:
  - navigation
---

# Release Notes

Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

RELEASE_NOTES_HEADER = "---\nhide:\n  - navigation\n---\n\n# Release Notes\n\n"

def notes_header_ok(path: Path) -> bool:
    return path.read_text(encoding="utf-8").startswith(RELEASE_NOTES_HEADER)

Try / catch

try:
    updated = update_release_notes(content, version, d, release_notes_file)
except RuntimeError as e:
    if "must start with" in str(e):
        raise SystemExit(f"Release notes header malformed: {e}") from e
    raise

Prevention

When it happens

Trigger: Calling prepare when release_notes_file (docs/en/docs/release-notes.md typically) has had its front-matter block edited, the '# Release Notes' title changed, or trailing whitespace/newlines disturbed the exact prefix match at scripts/prepare_release.py:65.

Common situations: A doc tool reformatted the front matter. The title was translated or renamed. A blank line was removed between front matter and the H1.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/e6205650a0c5aa48. Report an issue: GitHub.