mvanhorn/last30days-skill · error · SystemExit
[Planner] Cannot read --plan file: {exc}\n
Error message
[Planner] Cannot read --plan file: {exc}\n What it means
The --plan handling in run_topic: when the argument names an existing file, it is read as UTF-8; OSError or UnicodeDecodeError prints [Planner] Cannot read --plan file and exits 2. As with [12], the file branch only applies when os.path.isfile(plan_str) is true — otherwise the string is parsed as inline JSON.
Source
Thrown at skills/last30days/scripts/last30days.py:3252
try:
x_related = [h.strip() for h in args.x_related.split(",") if h.strip()] if args.x_related else None
subreddits = [s.strip().removeprefix("r/") for s in args.subreddits.split(",") if s.strip()] if args.subreddits else None
dedicated_subreddits = [s.strip().removeprefix("r/") for s in args.dedicated_subreddits.split(",") if s.strip()] if args.dedicated_subreddits else None
tiktok_hashtags = [h.strip().lstrip("#") for h in args.tiktok_hashtags.split(",") if h.strip()] if args.tiktok_hashtags else None
tiktok_creators = [c.strip().lstrip("@") for c in args.tiktok_creators.split(",") if c.strip()] if args.tiktok_creators else None
ig_creators = [c.strip().lstrip("@") for c in args.ig_creators.split(",") if c.strip()] if args.ig_creators else None
# Parse external plan if provided via --plan flag
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).View on GitHub (pinned to c7460f6114)
Solutions
- Pre-validate in the caller: python3 -c "open('plan.json', encoding='utf-8').read()" before invoking the engine.
- Write plans atomically (temp file + rename) so the engine never reads a partial file.
- Pass an absolute path to avoid cwd-dependent isfile() resolution.
Example fix
# before with open(path, 'w') as f: f.write(plan_json) # engine may read mid-write # after import tempfile, os tmp = path + '.tmp' with open(tmp, 'w', encoding='utf-8') as f: f.write(plan_json) os.replace(tmp, path)
Defensive patterns
Strategy: validation
Validate before calling
if os.path.isfile(plan_arg):
Path(plan_arg).read_text(encoding='utf-8') # fail in caller with better context Prevention
- Write plan files atomically (temp + os.replace) so the engine never reads a truncated file.
- Use absolute paths; verify read permissions before batch invocations.
When it happens
Trigger: --plan plan.json where the file is unreadable (permissions), is a directory-like special file, or contains invalid UTF-8 bytes; racing a writer that truncates the file.
Common situations: Plan files generated into /tmp and reaped before the engine subprocess reads them; files transferred with a bad encoding; permission-restricted plans from another user.
Related errors
- [CompetitorsPlan] Cannot read plan file: {exc}\n
- [Planner] Invalid --plan JSON: {exc}\n
- [last30days] Cannot read --synthesis-file: {exc}\n
- [CompetitorsPlan] Invalid JSON: {exc}\n
- Unknown search source in {flag_name}: {source}
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/736fccf3059805bb.
Report an issue: GitHub.