mvanhorn/last30days-skill · error · SystemExit
[CompetitorsPlan] Invalid JSON: {exc}\n
Error message
[CompetitorsPlan] Invalid JSON: {exc}\n What it means
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.
Source
Thrown at skills/last30days/scripts/last30days.py:808
Validation: top-level must be a dict; each value must be a dict. Unknown fields
in entry values log a warning but do not abort. Invalid JSON or non-dict shape
raises SystemExit(2) with a clear stderr message.
"""
if not raw:
return {}
plan_str = raw
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"[CompetitorsPlan] Cannot read plan file: {exc}\n")
raise SystemExit(2)
try:
parsed = json.loads(plan_str)
except json.JSONDecodeError as exc:
sys.stderr.write(f"[CompetitorsPlan] Invalid JSON: {exc}\n")
raise SystemExit(2)
if not isinstance(parsed, dict):
sys.stderr.write(
f"[CompetitorsPlan] Top-level must be a dict of "
f"{{entity: {{targeting}}}}, got {type(parsed).__name__}\n"
)
raise SystemExit(2)
known_fields = {
"x_handle", "x_related", "subreddits",
"github_user", "github_repos", "trustpilot_domain", "context",
}
normalized: dict[str, dict] = {}
for entity, entry in parsed.items():
if not isinstance(entry, dict):
sys.stderr.write(
f"[CompetitorsPlan] Entry for {entity!r} must be a dict, "
f"got {type(entry).__name__}; skipping.\n"
)
continueView on GitHub (pinned to c7460f6114)
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.
Example fix
# before (inline)
--competitors-plan "{'Acme': {'x_handle': 'acme'}}"
# after
--competitors-plan '{"Acme": {"x_handle": "acme"}}' Defensive patterns
Strategy: validation
Validate before calling
import json json.loads(Path(plan_path).read_text(encoding='utf-8') if os.path.isfile(plan_path) else plan_arg) # pre-validate before the engine run
Prevention
- 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.
When it happens
Trigger: --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.
Common situations: Agent-authored plans hand-formatting JSON; plan files from an editor with smart quotes; reading a file still being written by another process.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- [CompetitorsPlan] Cannot read plan file: {exc}\n
- [CompetitorsPlan] Top-level must be a dict of {entity: {targ
- [Planner] Invalid --plan JSON: {exc}\n
- [Competitors] --competitors-list is empty.\n
- [Competitors] --competitors must be >= {COMPETITORS_MIN} (go
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/e87af35916d90a3c.
Report an issue: GitHub.