{"record":{"id":"736fccf3059805bb","repo":"mvanhorn/last30days-skill","slug":"planner-cannot-read-plan-file-exc-n","errorCode":null,"errorMessage":"[Planner] Cannot read --plan file: {exc}\\n","messagePattern":"\\[Planner\\] Cannot read --plan file: (.+?)\\\\n","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/last30days.py","lineNumber":3252,"sourceCode":"    try:\n        x_related = [h.strip() for h in args.x_related.split(\",\") if h.strip()] if args.x_related else None\n        subreddits = [s.strip().removeprefix(\"r/\") for s in args.subreddits.split(\",\") if s.strip()] if args.subreddits else None\n        dedicated_subreddits = [s.strip().removeprefix(\"r/\") for s in args.dedicated_subreddits.split(\",\") if s.strip()] if args.dedicated_subreddits else None\n        tiktok_hashtags = [h.strip().lstrip(\"#\") for h in args.tiktok_hashtags.split(\",\") if h.strip()] if args.tiktok_hashtags else None\n        tiktok_creators = [c.strip().lstrip(\"@\") for c in args.tiktok_creators.split(\",\") if c.strip()] if args.tiktok_creators else None\n        ig_creators = [c.strip().lstrip(\"@\") for c in args.ig_creators.split(\",\") if c.strip()] if args.ig_creators else None\n        # Parse external plan if provided via --plan flag\n        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).","sourceCodeStart":3234,"sourceCodeEnd":3270,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/last30days.py#L3234-L3270","documentation":"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.","triggerScenarios":"--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.","commonSituations":"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.","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."],"exampleFix":"# before\nwith open(path, 'w') as f: f.write(plan_json)  # engine may read mid-write\n\n# after\nimport tempfile, os\ntmp = path + '.tmp'\nwith open(tmp, 'w', encoding='utf-8') as f: f.write(plan_json)\nos.replace(tmp, path)","handlingStrategy":"validation","validationCode":"if os.path.isfile(plan_arg):\n    Path(plan_arg).read_text(encoding='utf-8')  # fail in caller with better context","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write plan files atomically (temp + os.replace) so the engine never reads a truncated file.","Use absolute paths; verify read permissions before batch invocations."],"tags":["cli","filesystem","plan","planner"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}