{"record":{"id":"aa1c79ac878cf10c","repo":"bmad-code-org/BMAD-METHOD","slug":"each-extra-entry-must-be-a-json-object-got-it-aa1c79","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-brainstorming/scripts/brain.py","lineNumber":76,"sourceCode":"        for k in FIELDS:\n            r.setdefault(k, \"\")\n            r[k] = (r.get(k) or \"\").strip()\n    return rows\n\n\ndef load_extra(file: Path) -> list[dict]:\n    \"\"\"Merge-in techniques from a JSON overlay — a list of\n    {category, technique_name, description[, detail]} objects. This is how\n    customize.toml's `additional_techniques` become first-class across *every*\n    subcommand (categories/list/random/show/html), so the browse page and\n    category draws include them too, not just the in-chat flows.\"\"\"\n    data = json.loads(file.read_text(encoding=\"utf-8-sig\"))\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        rows.append({\n            \"category\": str(item.get(\"category\", \"\")).strip(),\n            \"technique_name\": str(item.get(\"technique_name\", \"\")).strip(),\n            \"description\": str(item.get(\"description\", \"\")).strip(),\n            \"detail\": str(item.get(\"detail\") or \"\").strip(),\n            \"provenance\": str(item.get(\"provenance\") or \"\").strip(),\n            \"good_for\": str(item.get(\"good_for\") or \"\").strip(),\n            \"audience\": str(item.get(\"audience\") or \"\").strip(),\n        })\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 technique_name (case-insensitive),\n    otherwise append — the same overlay semantics as pick_methods.py, so\n    customize.toml additional_* entries behave identically across sibling skills.\"\"\"\n    merged = list(rows)\n    index = {r[\"technique_name\"].lower(): i for i, r in enumerate(merged)}","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/core-skills/bmad-brainstorming/scripts/brain.py#L58-L94","documentation":"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.","triggerScenarios":"An array that mixes objects with a bare string, number, boolean, or null: `[\"SCAMPER\", {\"category\":\"...\"}]`.","commonSituations":"A note accidentally dropped into the array; a truncated entry missing its braces; a generator that emits technique names as strings.","solutions":["Find the element shown in `{item!r}` and turn it into an object with at least `category`, `technique_name`, and `description`.","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`.","Edit the overlay through a typed authoring path rather than raw JSON."],"exampleFix":"# before\n[\n  \"SCAMPER\",\n  {\"category\":\"divergent\",\"technique_name\":\"Six Hats\",\"description\":\"...\"}\n]\n\n# after\n[\n  {\"category\":\"divergent\",\"technique_name\":\"SCAMPER\",\"description\":\"...\"},\n  {\"category\":\"divergent\",\"technique_name\":\"Six Hats\",\"description\":\"...\"}\n]","handlingStrategy":"type-guard","validationCode":"import json\ndata = json.loads(Path(file).read_text(encoding='utf-8-sig'))\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(Path(args.extra))\nexcept ValueError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Lint each element before running.","Use a JSON schema requiring array-of-object.","Author techniques via a typed editor, not raw JSON."],"tags":["json","validation","brainstorming","config","cli-input"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}