{"record":{"id":"e87af35916d90a3c","repo":"mvanhorn/last30days-skill","slug":"competitorsplan-invalid-json-exc-n","errorCode":null,"errorMessage":"[CompetitorsPlan] Invalid JSON: {exc}\\n","messagePattern":"\\[CompetitorsPlan\\] Invalid JSON: (.+?)\\\\n","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/last30days.py","lineNumber":808,"sourceCode":"    Validation: top-level must be a dict; each value must be a dict. Unknown fields\n    in entry values log a warning but do not abort. Invalid JSON or non-dict shape\n    raises SystemExit(2) with a clear stderr message.\n    \"\"\"\n    if not raw:\n        return {}\n    plan_str = raw\n    if os.path.isfile(plan_str):\n        try:\n            with open(plan_str, encoding=\"utf-8\") as f:\n                plan_str = f.read()\n        except (OSError, UnicodeDecodeError) 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\", \"trustpilot_domain\", \"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","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/last30days.py#L790-L826","documentation":"After optionally reading a plan file, parse_competitors_plan() runs json.loads on the string (file contents or inline JSON). A JSONDecodeError prints [CompetitorsPlan] Invalid JSON with position info and exits 2 — no partial or default plan is used, because silently dropping targeting would burn a paid competitors run with wrong sources.","triggerScenarios":"--competitors-plan with inline JSON containing trailing commas, single quotes, or unescaped newlines; a plan file that is JSONL, YAML, or truncated mid-write; a plan file whose content was appended to twice.","commonSituations":"Agent-authored plans hand-formatting JSON; plan files from an editor with smart quotes; reading a file still being written by another process.","solutions":["Validate the JSON before invoking: python3 -m json.tool plan.json.","Fix the syntax error at the reported line/column in the message.","Generate plans with json.dumps rather than string templating."],"exampleFix":"# before (inline)\n--competitors-plan \"{'Acme': {'x_handle': 'acme'}}\"\n\n# after\n--competitors-plan '{\"Acme\": {\"x_handle\": \"acme\"}}'","handlingStrategy":"validation","validationCode":"import json\njson.loads(Path(plan_path).read_text(encoding='utf-8') if os.path.isfile(plan_path) else plan_arg)  # pre-validate before the engine run","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always run python3 -m json.tool on plan files before invoking the engine.","Generate plans with json.dumps, not string templates or LLM free-form text."],"tags":["cli","json","competitors","plan"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}