bmad-code-org/BMAD-METHOD · error · ValueError

each --extra entry must be a JSON object, got: {item!r}

Error message

each --extra entry must be a JSON object, got: {item!r}

What it means

brain.py confirms each element of the techniques overlay array is a dict and raises with the offending `{item!r}`. The next line reads `item.get("category")`, `.get("technique_name")`, etc., which only dicts support. A scalar/null element would otherwise crash with `AttributeError` mid-build.

Source

Thrown at src/core-skills/bmad-brainstorming/scripts/brain.py:76

        for k in FIELDS:
            r.setdefault(k, "")
            r[k] = (r.get(k) or "").strip()
    return rows


def load_extra(file: Path) -> list[dict]:
    """Merge-in techniques from a JSON overlay — a list of
    {category, technique_name, description[, detail]} objects. This is how
    customize.toml's `additional_techniques` become first-class across *every*
    subcommand (categories/list/random/show/html), so the browse page and
    category draws include them too, not just the in-chat flows."""
    data = json.loads(file.read_text(encoding="utf-8-sig"))
    if not isinstance(data, list):
        raise ValueError("--extra must be a JSON array of objects")
    rows = []
    for item in data:
        if not isinstance(item, dict):
            raise ValueError(f"each --extra entry must be a JSON object, got: {item!r}")
        rows.append({
            "category": str(item.get("category", "")).strip(),
            "technique_name": str(item.get("technique_name", "")).strip(),
            "description": str(item.get("description", "")).strip(),
            "detail": str(item.get("detail") or "").strip(),
            "provenance": str(item.get("provenance") or "").strip(),
            "good_for": str(item.get("good_for") or "").strip(),
            "audience": str(item.get("audience") or "").strip(),
        })
    return rows


def merge_extra(rows: list[dict], extras: list[dict]) -> list[dict]:
    """Extras replace a catalog row with the same technique_name (case-insensitive),
    otherwise append — the same overlay semantics as pick_methods.py, so
    customize.toml additional_* entries behave identically across sibling skills."""
    merged = list(rows)
    index = {r["technique_name"].lower(): i for i, r in enumerate(merged)}

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Find the element shown in `{item!r}` and turn it into an object with at least `category`, `technique_name`, and `description`.
  2. Lint every element: `python -c "import json,sys;[print(i,type(x).__name__) for i,x in enumerate(json.load(open(sys.argv[1])))]" file.json`.
  3. Edit the overlay through a typed authoring path rather than raw JSON.

Example fix

# before
[
  "SCAMPER",
  {"category":"divergent","technique_name":"Six Hats","description":"..."}
]

# after
[
  {"category":"divergent","technique_name":"SCAMPER","description":"..."},
  {"category":"divergent","technique_name":"Six Hats","description":"..."}
]
Defensive patterns

Strategy: type-guard

Validate before calling

import json
data = json.loads(Path(file).read_text(encoding='utf-8-sig'))
assert isinstance(data, list) and all(isinstance(x, dict) for x in data), \
    'each --extra entry must be a JSON object'

Type guard

def is_object_array(v: object) -> bool:
    return isinstance(v, list) and all(isinstance(x, dict) for x in v)

Try / catch

try:
    extras = load_extra(Path(args.extra))
except ValueError as e:
    print(f"error: {e}", file=sys.stderr); sys.exit(2)

Prevention

When it happens

Trigger: An array that mixes objects with a bare string, number, boolean, or null: `["SCAMPER", {"category":"..."}]`.

Common situations: A note accidentally dropped into the array; a truncated entry missing its braces; a generator that emits technique names as strings.

Related errors


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