{"record":{"id":"8291f3c7240922f9","repo":"nexu-io/open-design","slug":"competitorsplan-top-level-must-be-a-dict-of-ent","errorCode":null,"errorMessage":"[CompetitorsPlan] Top-level must be a dict of {entity: {targeting}}, got {type(parsed).__name__}","messagePattern":"\\[CompetitorsPlan\\] Top-level must be a dict of (.+?)\\}, got (.+?)","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"design-templates/last30days/scripts/last30days.py","lineNumber":330,"sourceCode":"        return {}\n    plan_str = raw\n    if os.path.isfile(plan_str):\n        try:\n            plan_str = open(plan_str).read()\n        except OSError as exc:\n            sys.stderr.write(f\"[CompetitorsPlan] Cannot read plan file: {exc}\\n\")\n            raise SystemExit(2)\n    try:\n        parsed = json.loads(plan_str)\n    except json.JSONDecodeError as exc:\n        sys.stderr.write(f\"[CompetitorsPlan] Invalid JSON: {exc}\\n\")\n        raise SystemExit(2)\n    if not isinstance(parsed, dict):\n        sys.stderr.write(\n            f\"[CompetitorsPlan] Top-level must be a dict of \"\n            f\"{{entity: {{targeting}}}}, got {type(parsed).__name__}\\n\"\n        )\n        raise SystemExit(2)\n    known_fields = {\n        \"x_handle\", \"x_related\", \"subreddits\",\n        \"github_user\", \"github_repos\", \"context\",\n    }\n    normalized: dict[str, dict] = {}\n    for entity, entry in parsed.items():\n        if not isinstance(entry, dict):\n            sys.stderr.write(\n                f\"[CompetitorsPlan] Entry for {entity!r} must be a dict, \"\n                f\"got {type(entry).__name__}; skipping.\\n\"\n            )\n            continue\n        unknown = set(entry.keys()) - known_fields\n        if unknown:\n            sys.stderr.write(\n                f\"[CompetitorsPlan] Unknown fields in {entity!r}: \"\n                f\"{sorted(unknown)}; ignoring.\\n\"\n            )","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/design-templates/last30days/scripts/last30days.py#L312-L348","documentation":"After successfully parsing JSON, the sanitizer enforces the top-level shape: it must be a dict (object) whose keys are entity names and whose values are themselves dicts of targeting fields. If `parsed` is a list, string, number, bool, or null, it writes `[CompetitorsPlan] Top-level must be a dict of {entity: {targeting}}, got <type>` and raises SystemExit(2). This is a structural guard before any per-entry validation runs.","triggerScenarios":"`--plan '[{\"acme\": {}}]'` (a list of one dict), `--plan '\"acme\"'` (a bare string), or `--plan 'null'`.","commonSituations":"Authoring the plan as a JSON array because that felt natural for multiple competitors; export from a tool that produces a list; confusion between the comparison list (`--competitors-list`) shape and the plan shape.","solutions":["Wrap the data as an object keyed by entity name: `{\"acme\": {...}, \"beta\": {...}}`.","Move any per-entry fields (x_handle, subreddits, github_user, github_repos, context, x_related) inside each entity's value dict."],"exampleFix":"# before\n--plan '[{\"entity\":\"acme\",\"x_handle\":\"acme\"}]'\n# after\n--plan '{\"acme\": {\"x_handle\": \"acme\"}}'","handlingStrategy":"type-guard","validationCode":"if not isinstance(parsed, dict):\n    raise TypeError(f'plan top-level must be object, got {type(parsed).__name__}')","typeGuard":"def is_plan_dict(parsed: object) -> bool:\n    return isinstance(parsed, dict) and all(isinstance(v, dict) for v in parsed.values())","tryCatchPattern":null,"preventionTips":["Key the plan by entity name; never use a top-level array.","Validate structure with the type guard before invoking the run."],"tags":["cli","json","python","argument-validation","competitors","schema"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}