{"record":{"id":"fc1f4cb40556dd0e","repo":"bmad-code-org/BMAD-METHOD","slug":"each-extra-entry-must-be-a-json-object-got-it","errorCode":null,"errorMessage":"each --extra entry must be a JSON object, got: {item!r}","messagePattern":"each --extra entry must be a JSON object, got: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py","lineNumber":65,"sourceCode":"    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:\n        key = e[\"method_name\"].lower()\n        if key in index:\n            e = dict(e)\n            e[\"num\"] = e[\"num\"] or merged[index[key]][\"num\"]","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py#L47-L83","documentation":"After confirming the `--extra` overlay is a list, pick_methods.py iterates each element and requires every one to be a JSON object. This error names the offending element via `{item!r}`. It guards the next line, which calls `item.get(k)` for each field in FIELDS — only dicts have `.get`. A scalar or null element would otherwise raise an `AttributeError` deep in the merge.","triggerScenarios":"The overlay array contains a bare string, number, boolean, or null among the objects: `[\"Six Hats\", {\"method_name\":\"...\"}]`, or a trailing comma / JSON5-style element that deserializes to null.","commonSituations":"Appending a free-text note into the array by mistake; a truncated copy-paste that drops the braces off one entry; a generator that emits method names as strings instead of objects.","solutions":["Read the `{item!r}` value in the message and wrap that element in an object with at least `method_name`, `category`, `description`, and `output_pattern`.","Run the file through `python -c \"import json,sys;[print(type(x).__name__,x) for x in json.load(open(sys.argv[1]))]\" methods.json` to spot any non-dict element.","Regenerate the overlay from a typed source (CSV/typed config) rather than hand-editing JSON."],"exampleFix":"# before\n[\n  \"Devil's Advocate\",\n  {\"method_name\":\"Six Hats\",\"category\":\"...\"}\n]\n\n# after\n[\n  {\"method_name\":\"Devil's Advocate\",\"category\":\"challenge\",\"description\":\"...\",\"output_pattern\":\"...\"},\n  {\"method_name\":\"Six Hats\",\"category\":\"...\",\"description\":\"...\",\"output_pattern\":\"...\"}\n]","handlingStrategy":"type-guard","validationCode":"import json\ndata = json.loads(spec)\nassert isinstance(data, list) and all(isinstance(x, dict) for x in data), \\\n    'each --extra entry must be a JSON object'","typeGuard":"def is_object_array(v: object) -> bool:\n    return isinstance(v, list) and all(isinstance(x, dict) for x in v)","tryCatchPattern":"try:\n    extras = load_extra(args.extra)\nexcept ValueError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Lint each element type before running the command.","Author overlays with a typed/schema tool rather than free text.","Run the overlay through a JSON schema that requires array-of-object."],"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"}