tiangolo/fastapi · error · RuntimeError
Release notes already contain a section for {version}
Error message
Release notes already contain a section for {version} What it means
Raised by update_release_notes() in scripts/prepare_release.py:70 when the release notes already contain a '## X.Y.Z' or '## X.Y.Z (date)' heading for the version being prepared (regex at scripts/prepare_release.py:69). The release process must not create duplicate version sections.
Source
Thrown at scripts/prepare_release.py:70
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:
version_heading = re.compile(rf"(?m)^## {re.escape(version)}(?: \([^)]+\))?$")
match = version_heading.search(content)
if not match:
raise RuntimeError(View on GitHub (pinned to 3e8d1526d8)
Solutions
- Check if the version was already released: python scripts/prepare_release.py release-notes --version-file ... --release-notes-file ...
- If the section is a stale draft, delete it and re-run prepare.
- If the version is genuinely already released, skip prepare for that version.
Example fix
<!-- before: duplicate 1.2.3 section --> ## 1.2.3 (2026-01-01) - first ## 1.2.3 (2026-01-02) - second <!-- after: keep only one --> ## 1.2.3 (2026-01-01) - first - second
Defensive patterns
Strategy: validation
Validate before calling
import re
from pathlib import Path
def version_section_exists(path: Path, version: str) -> bool:
pat = re.compile(rf"(?m)^## {re.escape(version)}(?: \([^)]+\))?$")
return pat.search(path.read_text(encoding="utf-8")) is not None Try / catch
try:
updated = update_release_notes(content, version, d, release_notes_file)
except RuntimeError as e:
if "already contain a section" in str(e):
raise SystemExit(f"Version {version} already released: {e}") from e
raise Prevention
- Run prepare once per release; do not re-run after the section is added.
- If a previous run half-completed, revert the release notes file before retrying.
- Check release-notes for the version before running prepare.
When it happens
Trigger: Running prepare twice for the same version, or for a version whose section was already added manually. The VERSION_HEADING_PATTERN detects an existing heading identical to the target version.
Common situations: A previous prepare run partially completed (section added, version file not bumped). Re-running a CI job that re-triggers prepare. Manual draft of the section before the tool runs.
Related errors
- Invalid version: {version!r}. Expected format: X.Y.Z
- Expected exactly one __version__ assignment in {version_file
- New version {version} must be greater than current version {
- {release_notes_file} must start with {RELEASE_NOTES_HEADER!r
- {release_notes_file} must start with {latest_header!r}
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/54d50a271b2ff7f3.
Report an issue: GitHub.