{"record":{"id":"7bc40b0e5c39ab29","repo":"bmad-code-org/BMAD-METHOD","slug":"failed-to-read-path-error","errorCode":null,"errorMessage":"failed to read {path}: {error}","messagePattern":"failed to read (.+?): (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"src/scripts/config_utils.py","lineNumber":31,"sourceCode":"\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:\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:","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/config_utils.py#L13-L49","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check permissions and ownership: `ls -l <path>` and `id`.","Fix ownership/permissions: `chmod +r <path>` or `chown <user>:<group> <path>`.","If on a network mount, remount or refresh credentials and retry.","Rule out a TOCTOU deletion by confirming no other process removes the file during the run."],"exampleFix":"# before: file owned by root, mode 0600, tool runs as app user\nsudo chown app:app _bmad/config.toml && chmod 0644 _bmad/config.toml\n# after: tool can read the layer","handlingStrategy":"validation","validationCode":"import os\nassert os.access(path, os.R_OK), f'cannot read {path}: check permissions'","typeGuard":null,"tryCatchPattern":"try:\n    load_toml(path, required=required)\nexcept ConfigError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Ensure the runtime user owns or can read config files (chmod 0644).","On network mounts, validate readability before the run.","Avoid concurrent processes that delete config mid-run."],"tags":["toml","config","filesystem","permissions","io"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}