bmad-code-org/BMAD-METHOD · error · RenderError
duplicate review layer id `{identifier}`
Error message
duplicate review layer id `{identifier}` What it means
Review-layer id values must be unique within one array; the renderer uses id as the key for layer selection/merge, so duplicates are ambiguous. The seen set detects the second occurrence of an id and aborts with the duplicated identifier.
Source
Thrown at src/scripts/render_skill.py:83
raise RenderError(f"{label} must be a list, got {type(value).__name__}")
result = []
for index, item in enumerate(value):
result.append(_require_string(item, f"{label}[{index}]"))
return result
def _require_review_layers(value: Any, label: str) -> list[dict[str, str]]:
if not isinstance(value, list):
raise RenderError(f"{label} must be a list of tables")
result: list[dict[str, str]] = []
seen: set[str] = set()
for index, item in enumerate(value):
item_label = f"{label}[{index}]"
if not isinstance(item, dict):
raise RenderError(f"{item_label} must be a table")
identifier = _require_string(item.get("id"), f"{item_label}.id")
if identifier in seen:
raise RenderError(f"duplicate review layer id `{identifier}`")
seen.add(identifier)
layer = {
"id": identifier,
"name": _require_string(item.get("name", identifier), f"{item_label}.name"),
"instruction": _require_string(
item.get("instruction"), f"{item_label}.instruction", allow_empty=True
),
}
if "when" in item:
layer["when"] = _require_string(item["when"], f"{item_label}.when")
result.append(layer)
return result
def _load_sources(skill_dir: Path) -> dict[str, str]:
sources: dict[str, str] = {}
for candidate in sorted(skill_dir.rglob("*.md")):
if candidate.name == "SKILL.md":View on GitHub (pinned to b70486b9bd)
Solutions
- Find the duplicated id named in the message and rename one occurrence.
- To override an existing layer, rely on the keyed merge (same id replaces) instead of adding a second entry.
- Validate that ids are unique across all customization layers before rendering.
Example fix
# before [[workflow.review_layers]] id = "architect" [[workflow.review_layers]] id = "architect" # duplicate # after [[workflow.review_layers]] id = "architect" [[workflow.review_layers]] id = "pm"
Defensive patterns
Strategy: validation
Validate before calling
import tomllib
from pathlib import Path
def validate_unique_layer_ids(toml_path: Path, dotted: str) -> None:
data = tomllib.loads(Path(toml_path).read_text(encoding="utf-8"))
cur: object = data
for part in dotted.split("."):
cur = cur[part]
ids = [item.get("id") for item in cur if isinstance(item, dict)]
if len(ids) != len(set(ids)):
dupes = [i for i in ids if ids.count(i) > 1]
raise SystemExit(f"duplicate review layer ids: {sorted(set(dupes))}") Type guard
def layer_ids_unique(layers: list[dict]) -> bool:
ids = [l.get("id") for l in layers]
return len(ids) == len(set(ids)) Prevention
- Use distinct id values for each review layer.
- To override a base layer, reuse its id (keyed merge replaces) rather than adding a duplicate.
- Run an id-uniqueness check over merged customization before rendering.
When it happens
Trigger: Two [[workflow.review_layers]] blocks share the same id = "architect" in the final merged customization. Because _resolve_customization_value receives the already-merged list, any repeated id across customize.toml and the custom/<skill>*.toml overrides that survived merging triggers it.
Common situations: Copy-pasting a layer block without changing the id; appending a new layer in a user override using an id already present in the base; non-keyed merge path producing repeated ids.
Related errors
- {label} must be a list of tables
- {item_label} must be a table
- {label} has unsupported default type {type(default).__name__
- {label} must resolve to an absolute path: {resolved}
- missing config value `{key}`
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/922cff3cd0b8dc41.
Report an issue: GitHub.