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

This is a runtime interpreter-version guard, not a logic error. The script imports `load_central_config` from the local `config_utils` module, which itself does `import tomllib` (config_utils.py:5). `tomllib` is a standard-library module that ships only with Python 3.11 and newer. When run on Python < 3.11, that import raises `ModuleNotFoundError(name='tomllib')`; the wrapper narrows on `error.name == 'tomllib'`, prints a one-line actionable message, and terminates the process via `SystemExit(3)`. It exists because the repository declares no `pyproject.toml` / `requires-python` / `.python-version`, so this import-time check is the only thing keeping the tool from failing later with a confusing traceback.

Source

Thrown at src/scripts/resolve_config.py:21

# requires-python = ">=3.11"
# ///
"""Resolve BMad's four central TOML layers to JSON."""

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_central_config
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 extract_key(data, dotted_key: str):
    current = data
    for part in dotted_key.split("."):
        if isinstance(current, dict) and part in current:
            current = current[part]
        else:
            return _MISSING
    return current


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Resolve BMad central config using four-layer TOML merge."

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Run the script with Python 3.11+: `python3.11 src/scripts/resolve_config.py` (or `python3.12`). Verify with `python3 --version` first.
  2. Recreate the virtualenv on a 3.11+ base: `python3.12 -m venv .venv && source .venv/bin/activate`, then re-run.
  3. Pin the project to a 3.11+ interpreter in your version manager: `pyenv local 3.12` / `mise use python@3.12` / `asdf local python 3.12.x`.
  4. If the interpreter is fixed at < 3.11 and cannot be upgraded, install the `tomli` backport and add a shim: `pip install tomli` plus `if sys.version_info < (3,11): import tomli as tomllib` in config_utils.py (only if the project owner accepts backport support — this is a code change, not a config fix).
  5. On CI, bump the Python setup step: `actions/setup-python@v5` with `python-version: '3.11'` or newer, or update the Docker base image 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_config / config_utils here — tomllib is guaranteed present.
import subprocess, sys
subprocess.run([sys.executable, "src/scripts/resolve_config.py"], check=True)

Try / catch

# The guard raises SystemExit(3) at import time, so catch it only at a
# process/subprocess boundary — never around the import itself, which would
# mask a genuinely broken environment.
import subprocess, sys

proc = subprocess.run(
    [sys.executable, "src/scripts/resolve_config.py"],
    capture_output=True, text=True,
)
if proc.returncode == 3 and "tomllib" in proc.stderr:
    raise RuntimeError(
        "resolve_config needs Python 3.11+; current interpreter is "
        f"{sys.version.split()[0]}."
    )
raise SystemExit(proc.returncode)

Prevention

When it happens

Trigger: Invoking `resolve_config.py` (directly or through a wrapper) with an interpreter whose `sys.version_info < (3, 11)`. Concretely: `python3.10 src/scripts/resolve_config.py`, or any `#!/usr/bin/env python3` shebang that resolves to a 3.10-or-older `python3` on PATH; running the project's tests with the wrong interpreter; a venv created from a pre-3.11 base interpreter; or a CI image pinned to an older Python. The error fires before any config file is read, so missing or malformed `*.toml` is NOT a cause.

Common situations: 1) System `python3` on older LTS distros (e.g. Ubuntu 20.04 ships 3.8, Debian 11 ships 3.9) resolving the shebang. 2) A virtualenv built from a pre-3.11 base interpreter while the developer assumes `python3` means 'latest'. 3) pyenv / asdf / mise shims pointing at an older Python. 4) CI matrix accidentally including 3.9/3.10, or a Docker base image like `python:3.10-slim`. 5) A toolchain manager (conda, uv, poetry) that created the env with an older default. 6) An alias `alias python=python3.10` lingering in the shell.

Related errors


AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13). Data as JSON: /api/errors/d8a08e443a8816d8. Report an issue: GitHub.