podman-container-tools/podman · error · ValueError
`end` without `if`
Error message
`end` without `if`
What it means
Raised when a '<< endif >>' token arrives with an empty stack — i.e. there is no open '<< if >>' to close in this file. The message text says 'end' but the token is literally 'endif'. Like the other control-flow errors, the if/endif pair must balance within a single rendered file (one options/*.md), because the stack is per-render() call.
Source
Thrown at hack/markdown-preprocess:94
# control blocks
if inner.startswith("if ") and len(inner[3:].strip().split(" ")) in [1, 2]:
cond = inner[3:].strip()
stack.append({"active": is_active() and truthy(cond), "seen_else": False})
continue
if inner == "else":
if not stack:
raise ValueError("`else` without `if`")
frame = stack[-1]
if frame["seen_else"]:
raise ValueError("multiple `else` in the same `if`")
frame["seen_else"] = True
parent_active = all(f["active"] for f in stack[:-1])
frame["active"] = parent_active and not frame["active"]
continue
if inner == "endif":
if not stack:
raise ValueError("`end` without `if`")
stack.pop()
continue
# inline "X if cond else Y" ---
if " if " in inner and " else " in inner:
try:
# split by " if " then " else "
then_part, rest = inner.split(" if ", 1)
cond, else_part = rest.split(" else ", 1)
cond = cond.strip()
chosen = then_part if truthy(cond) else else_part
if is_active():
out.append(chosen.strip().strip("'\""))
except Exception as e:
raise ValueError(f"Invalid inline if/else syntax: {inner}") from e
continue
# Unrecognized token (e.g. `<<a|b>>` for replace_type, or `<<subcommand>>`):View on GitHub (pinned to a2409076ef)
Solutions
- Count '<< if ' vs '<< endif >>' occurrences in the file and delete the surplus endif
- Verify the opener spelling: tokens must be lowercase 'if '/'else'/'endif' with single spaces
- Keep the whole if/endif group inside one options/*.md file
Example fix
# before << if is_quadlet >>A<< endif >> << endif >> # after << if is_quadlet >>A<< endif >>
Defensive patterns
Strategy: validation
Validate before calling
import re, pathlib
def lint(text, name):
stack = 0
for m in re.finditer(r'<<(.*?)>>', text, re.DOTALL):
inner = m.group(1).strip()
if inner.startswith('if ') and len(inner[3:].split()) in (1, 2):
stack += 1
elif inner == 'else':
assert stack > 0, f'{name}: `else` without `if` near offset {m.start()}'
elif inner == 'endif':
assert stack > 0, f'{name}: `end` without `if` near offset {m.start()}'
stack -= 1
assert stack == 0, f'{name}: {stack} unclosed `if` block(s)'
for p in pathlib.Path('docs/source/markdown/options').glob('*.md'):
lint(p.read_text(), str(p)) Prevention
- Match counts per file: [ "$(grep -c '<< if ' f)" -eq "$(grep -c '<< endif >>' f)" ]
- Tokens are case-sensitive lowercase ('if', 'else', 'endif') — avoid editor auto-capitalization in comments
- Keep if/endif pairs within one option file; never split them across the .md.in and the option file
When it happens
Trigger: An extra '<< endif >>' left after deleting an '<< if >>' line; a typo'd opener that is not recognized ('<< IF is_quadlet >>', '<<if is_quadlet>>' without the space after 'if' fails startswith('if ')... note '<<if ...>>' has inner='if is_quadlet' which does start with 'if ' so only case variants break it; '<< endif >>' after the block was already closed; if opened in one option file and endif placed in another.
Common situations: Deleting or commenting out an if-line while leaving its endif; splitting conditional content between two option files or between the option file and the .md.in man page; uppercase '<< ENDIF >>' (not recognized as closer, then the real extra endif triggers this).
Related errors
- `else` without `if`
- multiple `else` in the same `if`
- unclosed `if` block(s)
- undefined variable: {name}
- Invalid inline if/else syntax: {inner}
AI-assisted analysis of podman-container-tools/podman@a2409076ef (2026-08-15).
Data as JSON: /api/errors/45dd919273661a28.
Report an issue: GitHub.