{"record":{"id":"f22b33dead1f99a7","repo":"bmad-code-org/BMAD-METHOD","slug":"toml-layer-did-not-parse-to-a-table-path","errorCode":null,"errorMessage":"TOML layer did not parse to a table: {path}","messagePattern":"TOML layer did not parse to a table: (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"src/scripts/config_utils.py","lineNumber":33,"sourceCode":"\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:\n                value = item[candidate]\n                if not isinstance(value, str):\n                    raise ConfigError(\n                        f\"keyed array identifier `{candidate}` must be a string, \"\n                        f\"got {type(value).__name__}\"\n                    )\n                if not value:\n                    raise ConfigError(\n                        f\"keyed array identifier `{candidate}` must not be empty\"","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/config_utils.py#L15-L51","documentation":"Defensive guard at the end of `load_toml`: after a successful `tomllib.load`, the result must be a `dict` (a TOML top-level table). `tomllib` always returns a dict for well-formed TOML, so in practice this is nearly unreachable — it would only fire on a future/alternate parser that returns a non-dict root, or if `load_toml` is repurposed with a parser that can yield a bare array. Treat it as an invariant assertion, not an expected user error.","triggerScenarios":"Swapping `tomllib` for a parser that returns a list for a top-level TOML array; an internal refactor that bypasses the table contract; a corrupt parse path that returns None.","commonSituations":"Essentially never in normal use. If it appears, suspect a custom monkeypatch of `tomllib` or a hand-edited `config_utils.py`.","solutions":["Confirm you are using stdlib `tomllib` (Python 3.11+) and not a third-party substitute.","Ensure the file genuinely has key/value tables at the top level, not a bare TOML array document.","If you wrapped the parser, make sure it still returns a dict root."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import tomllib\nparsed = tomllib.load(open(path,'rb'))\nassert isinstance(parsed, dict), f'TOML layer did not parse to a table: {path}'","typeGuard":"def parses_to_table(path) -> bool:\n    return isinstance(tomllib.load(open(path,'rb')), dict)","tryCatchPattern":"try:\n    load_toml(path, required=required)\nexcept ConfigError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Use stdlib tomllib; do not swap in a parser that can return a non-dict root.","Keep TOML top-level as key/value tables."],"tags":["toml","config","invariant","defensive","bmad"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}