bmad-code-org/BMAD-METHOD · error · ConfigError
TOML layer is not a file: {path}
Error message
TOML layer is not a file: {path} What it means
`load_toml` raises this when the path exists and passes `path.exists()` but `path.is_file()` is False — i.e. the path resolves to a directory, a broken symlink, or a non-regular file (socket/device/FIFO). It distinguishes 'present but not a file' from 'absent', because absence may be legitimate for optional layers but a directory masquerading as a config file is always a setup mistake.
Source
Thrown at src/scripts/config_utils.py:24
from pathlib import Path
from typing import Any, Iterable
class ConfigError(ValueError):
"""Raised when a present configuration layer cannot be used safely."""
_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:View on GitHub (pinned to b70486b9bd)
Solutions
- Inspect the path: `ls -la <path>` and confirm it is a directory or broken symlink.
- Remove or rename the directory and recreate the file: `rm -rf <path> && <create the TOML file>`.
- If it is a symlink, repoint it at the real TOML file with `ln -sf <target> <path>`.
- Audit any tool that writes into the `_bmad` tree to ensure it writes files, not folders, at config paths.
Example fix
# before: _bmad/config.toml is a directory rm -rf _bmad/config.toml # after: recreate as a file printf '[project]\nname = "x"\n' > _bmad/config.toml
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
p = Path(path)
assert not p.exists() or p.is_file(), f'TOML layer is not a file: {p}' Type guard
def is_regular_file(path) -> bool:
p = Path(path)
return p.exists() and p.is_file() Try / catch
try:
load_toml(path, required=required)
except ConfigError as e:
print(f"error: {e}", file=sys.stderr); sys.exit(2) Prevention
- Never mkdir at a config file path.
- Validate path.is_file() before calling load_toml for required layers.
- Audit generators that write into _bmad to ensure they emit files.
When it happens
Trigger: Someone `mkdir _bmad/config.toml` instead of creating the file; a symlink pointing at a directory; a build step that materialised a config name as a folder (e.g. per-skill output dir colliding with a config filename).
Common situations: A misconfigured generator that writes config 'files' as directories; symlink drift after a repo move; an accidental `git mv` that turned a file into a folder.
Related errors
- required TOML file not found: {path}
- failed to parse {path}: {error}
- failed to read {path}: {error}
- TOML layer did not parse to a table: {path}
- keyed array identifier `{candidate}` must be a string, got {
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/d56907a2d2dc6357.
Report an issue: GitHub.