tiangolo/fastapi · error · ValueError

Header levels do not match between document and original doc

Error message

Header levels do not match between document and original document (found {header_info['hashes']}, expected {original_header_info['hashes']}) for header №{header_no + 1} in line {header_info['line_no']}

What it means

Raised by `replace_header_permalinks` (scripts/doc_parsing_utils.py:234) as a ValueError when the count of permalinks matches but the heading LEVEL (number of '#') for a given header differs between the translated and original document. For example the original has `### Setup` but the translation has `## Setup`. The error names the found/expected hashes, the 1-based header index, and the line number.

Source

Thrown at scripts/doc_parsing_utils.py:234

    Fail if the number or level of headers does not match the original.
    """

    modified_text: list[str] = text.copy()

    if len(header_permalinks) != len(original_header_permalinks):
        raise ValueError(
            "Number of headers with permalinks does not match the number in the "
            "original document "
            f"({len(header_permalinks)} vs {len(original_header_permalinks)})"
        )

    for header_no in range(len(header_permalinks)):
        header_info = header_permalinks[header_no]
        original_header_info = original_header_permalinks[header_no]

        if header_info["hashes"] != original_header_info["hashes"]:
            raise ValueError(
                "Header levels do not match between document and original document"
                f" (found {header_info['hashes']}, expected {original_header_info['hashes']})"
                f" for header №{header_no + 1} in line {header_info['line_no']}"
            )
        line_no = header_info["line_no"] - 1
        hashes = header_info["hashes"]
        title = header_info["title"]
        permalink = original_header_info["permalink"]
        modified_text[line_no] = f"{hashes} {title}{permalink}"

    return modified_text


# Markdown links
# --------------------------------------------------------------------------------------


def extract_markdown_links(lines: list[str]) -> list[MarkdownLinkInfo]:

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Set the heading level to match the English original: use the same number of '#' for the corresponding header.
  2. Open both files side by side and align hashes header-by-header at the reported line number.
  3. Re-run the check after fixing; the error reports the next mismatch if more exist.

Example fix

// before (translation line 42)
## Setup {#setup}
// after (match English ###)
### Setup {#setup}
Defensive patterns

Strategy: validation

Validate before calling

from scripts.doc_parsing_utils import extract_header_permalinks

def header_levels_match(translated_lines, en_lines) -> bool:
    a = extract_header_permalinks(translated_lines)
    b = extract_header_permalinks(en_lines)
    return len(a) == len(b) and all(x['hashes'] == y['hashes'] for x, y in zip(a, b))

Type guard

def corresponding_headers_same_level(translated_lines, en_lines) -> bool:
    from scripts.doc_parsing_utils import extract_header_permalinks
    a = extract_header_permalinks(translated_lines)
    b = extract_header_permalinks(en_lines)
    return len(a) == len(b) and all(x['hashes'] == y['hashes'] for x, y in zip(a, b))

Prevention

When it happens

Trigger: Running the docs translation check where a translator changed `##` to `###` (or vice versa) for a heading while keeping the same total number of headers. Promoting/demoting a section's level during translation.

Common situations: Translators 'fixing' perceived heading hierarchy. Copy-pasting content that re-leveled headings. Inconsistent heading depth between a sub-section and its translation.

Related errors


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