mvanhorn/last30days-skill · error · SystemExit
[Planner] Invalid --plan JSON: {exc}\n
Error message
[Planner] Invalid --plan JSON: {exc}\n What it means
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.
Source
Thrown at skills/last30days/scripts/last30days.py:3260
external_plan = None
if args.plan:
import json as _json
plan_str = args.plan
if os.path.isfile(plan_str):
try:
with open(plan_str, encoding="utf-8") as f:
plan_str = f.read()
except (OSError, UnicodeDecodeError) as exc:
sys.stderr.write(f"[Planner] Cannot read --plan file: {exc}\n")
raise SystemExit(2)
try:
external_plan = _json.loads(plan_str)
except _json.JSONDecodeError as exc:
sys.stderr.write(f"[Planner] Invalid --plan JSON: {exc}\n")
# Fail fast instead of silently dropping to the internal planner
# and burning a paid run the user did not ask for. Mirrors the
# --plan file-read branch above and parse_competitors_plan.
raise SystemExit(2)
from lib import planner as _plan_validator
try:
_plan_validator.validate_external_plan(external_plan)
except ValueError as exc:
sys.stderr.write(f"[Planner] Invalid --plan schema: {exc}.\n")
raise SystemExit(2)
# Auto-resolve: use web search to discover subreddits/handles before planning.
# This is the engine-side equivalent of SKILL.md Steps 0.55/0.75 for platforms
# without WebSearch (OpenClaw, Codex, raw CLI).
repos_from_auto_resolve = False
trustpilot_domain_is_hint = False
# Resolve automatically for entity-shaped topics even without the flag.
# A person or company topic whose handle the user did not supply is the
# case where first-party evidence is hardest to protect: the handle is
# absent from the topic and may never appear in retrieved mentions, so
# nothing downstream can identify the subject's own posts. One web
# search closes that. If it returns nothing, pipeline.run skips the XView on GitHub (pinned to c7460f6114)
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).
Example fix
# before (inline)
--plan "{subreddits: ['a'], handles: ['b']}"
# after
--plan '{"subreddits": ["a"], "handles": ["b"]}' Defensive patterns
Strategy: validation
Validate before calling
import json plan = json.loads(Path(plan_path).read_text(encoding='utf-8')) from lib import planner as v v.validate_external_plan(plan) # catches syntax AND schema before any paid run
Prevention
- 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.
When it happens
Trigger: --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.
Common situations: LLM/agent emitting non-strict JSON; hand-edited plans; newline-delimited plan files; copying from a chat client that mangled quotes.
Related errors
- [CompetitorsPlan] Invalid JSON: {exc}\n
- [Planner] Cannot read --plan file: {exc}\n
- [CompetitorsPlan] Cannot read plan file: {exc}\n
- [CompetitorsPlan] Top-level must be a dict of {entity: {targ
- top-level plan must be an object
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/d0162c08a79e3be4.
Report an issue: GitHub.