{"record":{"id":"d56907a2d2dc6357","repo":"bmad-code-org/BMAD-METHOD","slug":"toml-layer-is-not-a-file-path","errorCode":null,"errorMessage":"TOML layer is not a file: {path}","messagePattern":"TOML layer is not a file: (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"src/scripts/config_utils.py","lineNumber":24,"sourceCode":"from pathlib import Path\nfrom typing import Any, Iterable\n\n\nclass ConfigError(ValueError):\n    \"\"\"Raised when a present configuration layer cannot be used safely.\"\"\"\n\n\n_KEYED_MERGE_FIELDS = (\"code\", \"id\")\n\n\ndef load_toml(path: Path, *, required: bool = False) -> dict[str, Any]:\n    \"\"\"Load a TOML table, allowing absence only for optional layers.\"\"\"\n    if not path.exists():\n        if required:\n            raise ConfigError(f\"required TOML file not found: {path}\")\n        return {}\n    if not path.is_file():\n        raise ConfigError(f\"TOML layer is not a file: {path}\")\n    try:\n        with path.open(\"rb\") as stream:\n            parsed = tomllib.load(stream)\n    except tomllib.TOMLDecodeError as error:\n        raise ConfigError(f\"failed to parse {path}: {error}\") from error\n    except OSError as error:\n        raise ConfigError(f\"failed to read {path}: {error}\") from error\n    if not isinstance(parsed, dict):\n        raise ConfigError(f\"TOML layer did not parse to a table: {path}\")\n    return parsed\n\n\ndef _detect_keyed_merge_field(items: list[Any]) -> str | None:\n    if not items or not all(isinstance(item, dict) for item in items):\n        return None\n    for candidate in _KEYED_MERGE_FIELDS:\n        if all(candidate in item for item in items):\n            for item in items:","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/config_utils.py#L6-L42","documentation":"`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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before: _bmad/config.toml is a directory\nrm -rf _bmad/config.toml\n# after: recreate as a file\nprintf '[project]\\nname = \"x\"\\n' > _bmad/config.toml","handlingStrategy":"validation","validationCode":"from pathlib import Path\np = Path(path)\nassert not p.exists() or p.is_file(), f'TOML layer is not a file: {p}'","typeGuard":"def is_regular_file(path) -> bool:\n    p = Path(path)\n    return p.exists() and p.is_file()","tryCatchPattern":"try:\n    load_toml(path, required=required)\nexcept ConfigError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["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."],"tags":["toml","config","filesystem","symlink","bmad"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}