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"
            )
            continue

View on GitHub (pinned to c7460f6114)

Solutions

  1. Validate the JSON before invoking: python3 -m json.tool plan.json.
  2. Fix the syntax error at the reported line/column in the message.
  3. 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

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

Related errors


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/e87af35916d90a3c. Report an issue: GitHub.