{"record":{"id":"16f94997373b1789","repo":"mvanhorn/last30days-skill","slug":"could-not-read-label-file-file-path-exc","errorCode":null,"errorMessage":"Could not read {label} file {file_path}: {exc}","messagePattern":"Could not read (.+?) file (.+?): (.+?)","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":621,"sourceCode":"    return PendingReport(\n        schema_version=str(version),\n        bundle_id=bundle_id,\n        generated_at=str(generated_at or \"\"),\n        run_ref=str(payload.get(\"run_ref\") or \"\"),\n        report=report,\n        angle_inputs=angle_inputs,\n        mock=bool(payload.get(\"mock\")),\n        path=path,\n    )\n\n\ndef _load_host_file(path: str | Path, label: str) -> dict[str, Any]:\n    \"\"\"Load a host-authored handoff file with strict top-level checks.\"\"\"\n    file_path = Path(path).expanduser()\n    try:\n        raw = file_path.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise HandoffContractError(\n            f\"Could not read {label} file {file_path}: {exc}\"\n        ) from exc\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise HandoffContractError(\n            f\"{label.capitalize()} file {file_path} is not valid JSON: {exc}\"\n        ) from exc\n    if not isinstance(payload, dict):\n        raise HandoffContractError(\n            f\"{label.capitalize()} file {file_path} must be a top-level JSON \"\n            f\"object, got {type(payload).__name__}.\"\n        )\n    return payload\n\n\ndef _require_bundle_binding(\n    payload: dict[str, Any],","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L603-L639","documentation":"`_load_host_file` (reader for host-authored handoff files: the leg-2 `--judgments` file and leg-3 `--angles` file) raises HandoffContractError when `Path(path).expanduser().read_text()` raises OSError — the file does not exist, is unreadable, or the path is invalid. Unlike the engine-written artifacts, this file is authored by the host/user, so the common case is simply a wrong path or missing file. The message includes the label ('judgments'/'angles'), the expanded path, and the OS error; the CLI maps it to exit 2.","triggerScenarios":"`--judgments missing.json` (typo'd filename); passing a directory instead of a file; permissions denying read; a `~`-relative path that fails to expand to a real file.","commonSituations":"Authoring judgments in an editor and never saving; running the CLI from a different cwd with a relative path; host harnesses writing the file to an unexpected location; quoting mistakes leaving a literal `~` unexpanded by the shell (here expanduser() handles it, but a wrong username like `~other/file` may not exist).","solutions":["Verify the path exactly as printed in the message with `ls -l` (the message shows the expanded path).","Use an absolute path for `--judgments` / `--angles` to eliminate cwd ambiguity.","Confirm the file was actually saved/written by the authoring step before invoking the leg.","If permissions are the issue, `chmod +r <file>`."],"exampleFix":"# before\npython3 last30days.py \"topic\" --discover --judgments judgemnets.json   # typo\n# after\npython3 last30days.py \"topic\" --discover --judgments /abs/path/judgments.json","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef host_file_ready(path_str: str) -> Path:\n    p = Path(path_str).expanduser().resolve()\n    if not p.is_file():\n        raise SystemExit(f\"{p} does not exist; author the file first\")\n    return p","typeGuard":null,"tryCatchPattern":"from lib import discovery_handoff\ntry:\n    payload = discovery_handoff._load_host_file(\"judgments.json\", \"judgments\")\nexcept discovery_handoff.HandoffContractError as exc:\n    if \"Could not read\" in exc.message:\n        # wrong path or permissions; the message shows the expanded path\n        ...","preventionTips":["Pass absolute paths to --judgments / --angles.","Assert the file exists and is non-empty in your wrapper before invoking the leg.","Save-and-close editor buffers before running; unsaved files are the #1 cause."],"tags":["discovery","handoff","host-file","filesystem","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}