bmad-code-org/BMAD-METHOD · error · ConfigError

failed to read {path}: {error}

Error message

failed to read {path}: {error}

What it means

`load_toml` catches `OSError` (broad: `PermissionError`, `FileNotFoundError` race, `IsADirectoryError`, disk/IO errors) during the `open`/`read` and re-raises as `ConfigError` with the path and OS message. The file exists and is a regular file, but the process cannot read it — almost always a permissions or filesystem problem rather than a content problem.

Source

Thrown at src/scripts/config_utils.py:31

_KEYED_MERGE_FIELDS = ("code", "id")


def load_toml(path: Path, *, required: bool = False) -> dict[str, Any]:
    """Load a TOML table, allowing absence only for optional layers."""
    if not path.exists():
        if required:
            raise ConfigError(f"required TOML file not found: {path}")
        return {}
    if not path.is_file():
        raise ConfigError(f"TOML layer is not a file: {path}")
    try:
        with path.open("rb") as stream:
            parsed = tomllib.load(stream)
    except tomllib.TOMLDecodeError as error:
        raise ConfigError(f"failed to parse {path}: {error}") from error
    except OSError as error:
        raise ConfigError(f"failed to read {path}: {error}") from error
    if not isinstance(parsed, dict):
        raise ConfigError(f"TOML layer did not parse to a table: {path}")
    return parsed


def _detect_keyed_merge_field(items: list[Any]) -> str | None:
    if not items or not all(isinstance(item, dict) for item in items):
        return None
    for candidate in _KEYED_MERGE_FIELDS:
        if all(candidate in item for item in items):
            for item in items:
                value = item[candidate]
                if not isinstance(value, str):
                    raise ConfigError(
                        f"keyed array identifier `{candidate}` must be a string, "
                        f"got {type(value).__name__}"
                    )
                if not value:

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Check permissions and ownership: `ls -l <path>` and `id`.
  2. Fix ownership/permissions: `chmod +r <path>` or `chown <user>:<group> <path>`.
  3. If on a network mount, remount or refresh credentials and retry.
  4. Rule out a TOCTOU deletion by confirming no other process removes the file during the run.

Example fix

# before: file owned by root, mode 0600, tool runs as app user
sudo chown app:app _bmad/config.toml && chmod 0644 _bmad/config.toml
# after: tool can read the layer
Defensive patterns

Strategy: validation

Validate before calling

import os
assert os.access(path, os.R_OK), f'cannot read {path}: check permissions'

Try / catch

try:
    load_toml(path, required=required)
except ConfigError as e:
    print(f"error: {e}", file=sys.stderr); sys.exit(2)

Prevention

When it happens

Trigger: The config file is mode `0600` owned by another user; a read-only mount; the file vanished between the `is_file()` check and the `open` (TOCTOU); a locked file on a network share; disk/IO failure.

Common situations: Running the tool as a different user than the one that created the config; CI in a restricted container; a NFS/CIFS mount with stale credentials; a concurrent process deleting the file mid-run.

Related errors


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