{"record":{"id":"c49ae5467e8622a0","repo":"bmad-code-org/BMAD-METHOD","slug":"extra-must-be-a-json-array-of-objects","errorCode":null,"errorMessage":"--extra must be a JSON array of objects","messagePattern":"--extra must be a JSON array of objects","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py","lineNumber":61,"sourceCode":"\n\ndef load(file: Path) -> list[dict]:\n    # utf-8-sig: tolerate BOM-prefixed catalogs (Excel \"CSV UTF-8\", Notepad)\n    with open(file, newline=\"\", encoding=\"utf-8-sig\") as f:\n        rows = list(csv.DictReader(f))\n    for r in rows:\n        for k in FIELDS:\n            r.setdefault(k, \"\")\n            r[k] = (r.get(k) or \"\").strip()\n    return rows\n\n\ndef load_extra(spec: str) -> list[dict]:\n    \"\"\"Parse the --extra overlay: a JSON array literal or a path to a JSON file.\"\"\"\n    text = spec if spec.lstrip().startswith(\"[\") else Path(spec).read_text(encoding=\"utf-8-sig\")\n    data = json.loads(text)\n    if not isinstance(data, list):\n        raise ValueError(\"--extra must be a JSON array of objects\")\n    rows = []\n    for item in data:\n        if not isinstance(item, dict):\n            raise ValueError(f\"each --extra entry must be a JSON object, got: {item!r}\")\n        row = {k: str(item.get(k) or \"\").strip() for k in FIELDS}\n        row[\"code\"] = str(item.get(\"code\") or \"\").strip()  # kept for traceability\n        rows.append(row)\n    return rows\n\n\ndef merge_extra(rows: list[dict], extras: list[dict]) -> list[dict]:\n    \"\"\"Extras replace a catalog row with the same method_name (case-insensitive),\n    otherwise append — so overrides can retune shipped methods or grow the catalog.\n    A replacement inherits the shipped row's num; appended extras get the next\n    free nums, so every merged method stays addressable by number.\"\"\"\n    merged = list(rows)\n    index = {r[\"method_name\"].lower(): i for i, r in enumerate(merged)}\n    for e in extras:","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py#L43-L79","documentation":"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`.","triggerScenarios":"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.","commonSituations":"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 `[{...}]`.","solutions":["Wrap a single object in an array: `--extra '[{\"method_name\":\"...\",\"category\":\"...\"}]'`.","If pointing at a file, open it and confirm the top-level character is `[`.","Validate the JSON with `python -m json.tool` and check the root type before passing it to `--extra`.","Build the overlay from `customize.toml`'s `additional_methods` via the project's own emitter rather than hand-editing."],"exampleFix":"# before\n--extra '{\"method_name\":\"Devil's Advocate\",\"category\":\"challenge\"}'\n\n# after: wrap in an array\n--extra '[{\"method_name\":\"Devil's Advocate\",\"category\":\"challenge\",\"description\":\"...\",\"output_pattern\":\"...\"}]'","handlingStrategy":"validation","validationCode":"import json\nspec = '{...}'  # or file contents\ndata = json.loads(spec if spec.lstrip().startswith('[') else open(path).read())\nassert isinstance(data, list), '--extra must be a JSON array of objects'","typeGuard":"def is_json_array(spec: str) -> bool:\n    try:\n        v = json.loads(spec)\n    except json.JSONDecodeError:\n        return False\n    return isinstance(v, list)","tryCatchPattern":"try:\n    extras = load_extra(args.extra)\nexcept ValueError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Always wrap --extra content in [ ], even for a single method.","Validate the overlay with python -m json.tool before passing it.","Prefer generating the overlay from customize.toml's additional_methods over hand-writing JSON."],"tags":["json","validation","elicitation","cli-input","config"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}