{"record":{"id":"d0162c08a79e3be4","repo":"mvanhorn/last30days-skill","slug":"planner-invalid-plan-json-exc-n","errorCode":null,"errorMessage":"[Planner] Invalid --plan JSON: {exc}\\n","messagePattern":"\\[Planner\\] Invalid --plan JSON: (.+?)\\\\n","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/last30days.py","lineNumber":3260,"sourceCode":"        external_plan = None\n        if args.plan:\n            import json as _json\n            plan_str = args.plan\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\"[Planner] Cannot read --plan file: {exc}\\n\")\n                    raise SystemExit(2)\n            try:\n                external_plan = _json.loads(plan_str)\n            except _json.JSONDecodeError as exc:\n                sys.stderr.write(f\"[Planner] Invalid --plan JSON: {exc}\\n\")\n                # Fail fast instead of silently dropping to the internal planner\n                # and burning a paid run the user did not ask for. Mirrors the\n                # --plan file-read branch above and parse_competitors_plan.\n                raise SystemExit(2)\n            from lib import planner as _plan_validator\n            try:\n                _plan_validator.validate_external_plan(external_plan)\n            except ValueError as exc:\n                sys.stderr.write(f\"[Planner] Invalid --plan schema: {exc}.\\n\")\n                raise SystemExit(2)\n\n        # Auto-resolve: use web search to discover subreddits/handles before planning.\n        # This is the engine-side equivalent of SKILL.md Steps 0.55/0.75 for platforms\n        # without WebSearch (OpenClaw, Codex, raw CLI).\n        repos_from_auto_resolve = False\n        trustpilot_domain_is_hint = False\n        # Resolve automatically for entity-shaped topics even without the flag.\n        # A person or company topic whose handle the user did not supply is the\n        # case where first-party evidence is hardest to protect: the handle is\n        # absent from the topic and may never appear in retrieved mentions, so\n        # nothing downstream can identify the subject's own posts. One web\n        # search closes that. If it returns nothing, pipeline.run skips the X","sourceCodeStart":3242,"sourceCodeEnd":3278,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/last30days.py#L3242-L3278","documentation":"The second --plan guard: json.loads on the plan string (file contents or inline JSON) raised JSONDecodeError, so the engine exits 2 rather than silently falling back to the internal planner — a deliberate fail-fast so a malformed plan never burns a paid run the user did not ask for. This mirrors parse_competitors_plan's behavior.","triggerScenarios":"--plan with inline JSON using single quotes/trailing commas; a plan file that is YAML, JSONL, or truncated; a file whose last write was interrupted mid-object.","commonSituations":"LLM/agent emitting non-strict JSON; hand-edited plans; newline-delimited plan files; copying from a chat client that mangled quotes.","solutions":["Validate first: python3 -m json.tool plan.json — fix any reported syntax error.","Produce plans programmatically with json.dumps, never string interpolation.","Check the plan against validate_external_plan's expected schema after parsing (the next guard catches schema errors separately)."],"exampleFix":"# before (inline)\n--plan \"{subreddits: ['a'], handles: ['b']}\"\n\n# after\n--plan '{\"subreddits\": [\"a\"], \"handles\": [\"b\"]}'","handlingStrategy":"validation","validationCode":"import json\nplan = json.loads(Path(plan_path).read_text(encoding='utf-8'))\nfrom lib import planner as v\nv.validate_external_plan(plan)  # catches syntax AND schema before any paid run","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate plan JSON with python3 -m json.tool and lib.planner.validate_external_plan before invoking.","Emit plans with json.dumps; never free-form LLM strings."],"tags":["cli","json","plan","planner"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}