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

--extra must be a JSON array of objects

Error message

--extra must be a JSON array of objects

What it means

pick_methods.py parses the `--extra` overlay (additional elicitation methods). The spec may be a JSON array literal or a path to a JSON file; after `json.loads` it must be a top-level JSON array. This error is raised when the JSON parsed successfully but the resulting value is not a list — it is an object, string, number, boolean, or null. The check is deliberately strict because downstream `merge_extra` iterates and indexes by `method_name`.

Source

Thrown at src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py:61


def load(file: Path) -> list[dict]:
    # utf-8-sig: tolerate BOM-prefixed catalogs (Excel "CSV UTF-8", Notepad)
    with open(file, newline="", encoding="utf-8-sig") as f:
        rows = list(csv.DictReader(f))
    for r in rows:
        for k in FIELDS:
            r.setdefault(k, "")
            r[k] = (r.get(k) or "").strip()
    return rows


def load_extra(spec: str) -> list[dict]:
    """Parse the --extra overlay: a JSON array literal or a path to a JSON file."""
    text = spec if spec.lstrip().startswith("[") else Path(spec).read_text(encoding="utf-8-sig")
    data = json.loads(text)
    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}")
        row = {k: str(item.get(k) or "").strip() for k in FIELDS}
        row["code"] = str(item.get("code") or "").strip()  # kept for traceability
        rows.append(row)
    return rows


def merge_extra(rows: list[dict], extras: list[dict]) -> list[dict]:
    """Extras replace a catalog row with the same method_name (case-insensitive),
    otherwise append — so overrides can retune shipped methods or grow the catalog.
    A replacement inherits the shipped row's num; appended extras get the next
    free nums, so every merged method stays addressable by number."""
    merged = list(rows)
    index = {r["method_name"].lower(): i for i, r in enumerate(merged)}
    for e in extras:

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Wrap a single object in an array: `--extra '[{"method_name":"...","category":"..."}]'`.
  2. If pointing at a file, open it and confirm the top-level character is `[`.
  3. Validate the JSON with `python -m json.tool` and check the root type before passing it to `--extra`.
  4. Build the overlay from `customize.toml`'s `additional_methods` via the project's own emitter rather than hand-editing.

Example fix

# before
--extra '{"method_name":"Devil's Advocate","category":"challenge"}'

# after: wrap in an array
--extra '[{"method_name":"Devil's Advocate","category":"challenge","description":"...","output_pattern":"..."}]'
Defensive patterns

Strategy: validation

Validate before calling

import json
spec = '{...}'  # or file contents
data = json.loads(spec if spec.lstrip().startswith('[') else open(path).read())
assert isinstance(data, list), '--extra must be a JSON array of objects'

Type guard

def is_json_array(spec: str) -> bool:
    try:
        v = json.loads(spec)
    except json.JSONDecodeError:
        return False
    return isinstance(v, list)

Try / catch

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

Prevention

When it happens

Trigger: Calling with `--extra '{"method_name": "..."}'` (a single object, not wrapped in `[ ]`), `--extra methods.json` where the file contains a JSON object or a bare string, or a YAML-style document that happens to be valid JSON but is a mapping.

Common situations: Hand-writing one extra method and forgetting the surrounding brackets; exporting a single record from another tool that serializes as an object; a templating step that emits `{}` instead of `[{...}]`.

Related errors


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