bmad-code-org/BMAD-METHOD · error · ConfigError
keyed array identifier `{candidate}` must not be empty
Error message
keyed array identifier `{candidate}` must not be empty What it means
Sibling to the non-string id check: `_detect_keyed_merge_field` also rejects an id that is an empty string. An empty key would collapse every entry onto the same identity slot during keyed merge, silently dropping all but the last. Raising here protects against data loss in the merged output.
Source
Thrown at src/scripts/config_utils.py:50
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:
raise ConfigError(
f"keyed array identifier `{candidate}` must not be empty"
)
return candidate
return None
def _merge_arrays(base: list[Any], override: list[Any]) -> list[Any]:
keyed_field = _detect_keyed_merge_field(base + override)
if keyed_field is None:
return list(base) + list(override)
result: list[Any] = []
index_by_key: dict[str, int] = {}
for item in base:
copied = dict(item)
index_by_key[copied[keyed_field]] = len(result)
result.append(copied)
for item in override:View on GitHub (pinned to b70486b9bd)
Solutions
- Give every entry a unique non-empty `id`/`code`.
- Remove the id field entirely if you want the arrays concatenated rather than identity-merged.
- Lint for blank ids: `python -c "import tomllib;d=tomllib.load(open('f','rb'));[print(i) for a in d.values() if isinstance(a,list) for i in a if isinstance(i,dict) and i.get('id')=='']"`.
- Regenerate the layer from a source that guarantees non-empty ids.
Example fix
# before [[review_layers]] id = "" name = "security" # after [[review_layers]] id = "security" name = "security"
Defensive patterns
Strategy: validation
Validate before calling
def no_blank_ids(items):
return all(i.get('id','').strip() != '' for i in items if isinstance(i,dict) and 'id' in i) Type guard
def has_nonempty_ids(items: list) -> bool:
return all(isinstance(i.get('id'), str) and i['id'].strip() for i in items if isinstance(i,dict) and 'id' in i) Try / catch
from config_utils import ConfigError
try:
structural_merge(base, override)
except ConfigError as e:
print(f"error: {e}", file=sys.stderr); sys.exit(2) Prevention
- Give every keyed entry a unique non-empty id.
- Remove the id field if concatenation is intended.
- Lint config for blank ids in CI.
When it happens
Trigger: A `[[...]]` entry with `id = ""` or `code = ""` in either the base or override layer; a template that left the id field empty; a record exported with a blank identifier.
Common situations: Copying a TOML block and forgetting to fill in the id; an exporter that emits the id column even when the source had no value; partial edit leaving a placeholder.
Related errors
- keyed array identifier `{candidate}` must be a string, got {
- required TOML file not found: {path}
- TOML layer is not a file: {path}
- failed to parse {path}: {error}
- TOML layer did not parse to a table: {path}
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/2e573c442785199c.
Report an issue: GitHub.