bmad-code-org/BMAD-METHOD · critical · SystemExit
error: Python 3.11+ is required (stdlib `tomllib` not found)
Error message
error: Python 3.11+ is required (stdlib `tomllib` not found).
What it means
Identical mechanism to its sibling in resolve_config.py: a runtime interpreter-version guard. This script imports `load_customization` from the local `config_utils` module, which performs `import tomllib` (config_utils.py:5). `tomllib` is standard-library only on Python 3.11+. On older interpreters the import raises `ModuleNotFoundError(name='tomllib')`; the wrapper matches `error.name == 'tomllib'`, writes the same one-line message, and exits with `SystemExit(3)`. The guard exists because the project has no `pyproject.toml` / `requires-python` declaration, so this import-time check is the only enforcement of the minimum interpreter.
Source
Thrown at src/scripts/resolve_customization.py:21
# requires-python = ">=3.11"
# ///
"""Resolve a skill's default, team, and user TOML customization layers."""
import argparse
import json
import sys
from pathlib import Path
# Installed scripts are consumer files, not a location for interpreter caches.
sys.dont_write_bytecode = True
try:
from config_utils import ConfigError, load_customization
except ModuleNotFoundError as error:
if error.name != "tomllib":
raise
sys.stderr.write("error: Python 3.11+ is required (stdlib `tomllib` not found).\n")
raise SystemExit(3) from None
_MISSING = object()
def find_project_root(start: Path) -> Path | None:
current = start.resolve()
while True:
if (current / "_bmad").exists() or (current / ".git").exists():
return current
if current.parent == current:
return None
current = current.parent
def extract_key(data, dotted_key: str):
current = data
for part in dotted_key.split("."):View on GitHub (pinned to b70486b9bd)
Solutions
- Run the script with Python 3.11+: `python3.11 src/scripts/resolve_customization.py` (or `python3.12`). Confirm with `python3 --version` first.
- Recreate the virtualenv on a 3.11+ base: `python3.12 -m venv .venv && source .venv/bin/activate`, then re-run.
- Pin the interpreter via your version manager: `pyenv local 3.12` / `mise use python@3.12` / `asdf local python 3.12.x`.
- If locked to < 3.11 and upgrading is impossible, install the `tomli` backport and add `if sys.version_info < (3,11): import tomli as tomllib` in config_utils.py — only with the project owner's sign-off, since this is a code change, not configuration.
- On CI, raise the setup-python version: `python-version: '3.11'` or newer, or switch the Docker base to `python:3.11-slim`+.
Example fix
# before — shebang floats to whatever 'python3' resolves to
#!/usr/bin/env python3
# after — pin to a 3.11+ interpreter explicitly
#!/usr/bin/env python3.12
# or, in CI (.github/workflows/*.yml)
# before
- uses: actions/setup-python@v5
with:
python-version: '3.10'
# after
- uses: actions/setup-python@v5
with:
python-version: '3.12' Defensive patterns
Strategy: validation
Validate before calling
import sys
MIN_PY = (3, 11)
if sys.version_info < MIN_PY:
sys.exit(
f"error: Python {MIN_PY[0]}.{MIN_PY[1]}+ is required "
f"(running {sys.version_info.major}.{sys.version_info.minor})."
)
# Safe to invoke resolve_customization / config_utils here.
import subprocess, sys
subprocess.run([sys.executable, "src/scripts/resolve_customization.py"], check=True) Try / catch
# SystemExit(3) fires at import time; catch it only at a subprocess boundary,
# never around the import, or you hide a genuinely broken environment.
import subprocess, sys
proc = subprocess.run(
[sys.executable, "src/scripts/resolve_customization.py"],
capture_output=True, text=True,
)
if proc.returncode == 3 and "tomllib" in proc.stderr:
raise RuntimeError(
"resolve_customization needs Python 3.11+; current interpreter is "
f"{sys.version.split()[0]}."
)
raise SystemExit(proc.returncode) Prevention
- Pin the project interpreter explicitly: add a `.python-version` file (pyenv/asdf/mise) or `requires-python = '>=3.11'` in a `pyproject.toml` so the requirement is enforced at install time.
- Invoke the script via the venv's absolute interpreter (`$VIRTUAL_ENV/bin/python` / `python3.12`) rather than a bare `python3` whose meaning varies per machine.
- Add a preflight assertion in any orchestrator: `assert sys.version_info >= (3, 11), 'Python 3.11+ required (tomllib).'`.
- Lock CI to a 3.11+ matrix and fail fast on older versions; do not ship green-on-3.10 and broken-on-3.12 (or vice versa).
- State the minimum interpreter in the README beside the install command so the venv is built with the right base.
When it happens
Trigger: Invoking `resolve_customization.py` (directly or via a wrapper) with an interpreter where `sys.version_info < (3, 11)` — e.g. `python3.10 src/scripts/resolve_customization.py`, or a `#!/usr/bin/env python3` shebang resolving to an older `python3` on PATH; running the customization-resolution test suite under the wrong interpreter; a pre-3.11 venv; a CI image pinned to 3.9/3.10. The error is raised at import time before any `_bmad`/`.git` project-root lookup or customization file is touched, so customization-file contents are NOT a cause.
Common situations: 1) Default `python3` on older LTS distros (Ubuntu 20.04 → 3.8, Debian 11 → 3.9) satisfying the shebang. 2) A venv built from a pre-3.11 base interpreter. 3) pyenv / asdf / mise shims still pointing at an older Python after an upgrade. 4) CI matrix that still lists 3.9/3.10, or a Docker base like `python:3.10-slim`. 5) A poetry/uv/conda env materialised against an older default Python. 6) A shell alias or PATH ordering that shadows the 3.11+ interpreter with an older one.
Related errors
- error: Python 3.11+ is required (stdlib `tomllib` not found)
- {label} must be a list of tables
- {item_label} must be a table
- duplicate review layer id `{identifier}`
- render source escapes skill directory: {name}
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/5e0ea0d47892d7cb.
Report an issue: GitHub.