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

  1. Find the duplicated id named in the message and rename one occurrence.
  2. To override an existing layer, rely on the keyed merge (same id replaces) instead of adding a second entry.
  3. 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

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


AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13). Data as JSON: /api/errors/922cff3cd0b8dc41. Report an issue: GitHub.