{"record":{"id":"a2c5781f905953bf","repo":"mvanhorn/last30days-skill","slug":"could-not-read-label-lower-path-exc","errorCode":null,"errorMessage":"Could not read {label.lower()} {path}: {exc}","messagePattern":"Could not read (.+?) (.+?): (.+?)","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":398,"sourceCode":"\ndef _parse_handoff_envelope(\n    path: Path,\n    *,\n    label: str,\n    kind: str,\n    schema_version: str,\n    remedy: str,\n    missing_id_context: str,\n    stale_context: str,\n) -> tuple[dict[str, Any], str, Any]:\n    \"\"\"Shared strict top-level validation for the two engine-written handoff\n    files (nominations bundle, pending report): readable, valid JSON object,\n    right kind and schema version, bundle_id present, within TTL. Returns\n    (payload, bundle_id, generated_at).\"\"\"\n    try:\n        raw = path.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise HandoffContractError(\n            f\"Could not read {label.lower()} {path}: {exc}\"\n        ) from exc\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise HandoffContractError(\n            f\"{label} {path} is not valid JSON: {exc}\"\n        ) from exc\n    if not isinstance(payload, dict):\n        raise HandoffContractError(\n            f\"{label} {path} must be a top-level JSON object, \"\n            f\"got {type(payload).__name__}.\"\n        )\n    version = payload.get(\"schema_version\")\n    if version != schema_version:\n        raise HandoffContractError(\n            f\"{label} {path} has schema version {version!r}; this \"\n            f\"build reads {schema_version!r}. {remedy}\"","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L380-L416","documentation":"`_parse_handoff_envelope` (shared by the nominations-bundle and pending-report readers) raises HandoffContractError when `path.read_text` raises OSError — the handoff file exists but cannot be read (permissions, I/O error, race where it was deleted between the exists() check and the open). The message names the file kind ('nominations bundle' / 'pending discovery report'), the path, and the OS error; the CLI maps it to exit 2.","triggerScenarios":"`discover-nominations.json` or `discover-pending.json` present but mode 000 / owned by another user; a network mount dropping mid-read; the file deleted by a concurrent process after discovery but before parse.","commonSituations":"Root-created files from earlier sudo runs; NFS/FUSE latency or auth expiry on the save dir; two concurrent engine invocations where one cleans state while the other reads; container read-only volume mounts.","solutions":["Inspect the exact path in the message: `ls -l <path>` for mode/owner and `cat <path>` to reproduce the read failure.","Fix permissions (`chmod 644` / `chown`) or remount the volume read-write.","Re-run the leg that produces the file (nominate-only sweep, or resume leg for the pending report) to rewrite it cleanly.","Serialize concurrent discovery runs on the same save dir — the handoff files are single-writer state."],"exampleFix":"# before: bundle written under sudo, unreadable as user\n# after\nsudo chown \"$(id -un):$(id -gn)\" /path/to/save-dir/discover-nominations.json && python3 last30days.py \"topic\" --discover --judgments judgments.json --save-dir /path/to/save-dir","handlingStrategy":"try-catch","validationCode":"from pathlib import Path\n\ndef readable_file(path: Path) -> bool:\n    try:\n        with path.open(\"r\", encoding=\"utf-8\"):\n            return True\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"from lib import discovery_handoff\ntry:\n    bundle = discovery_handoff.load_nominations_bundle(save_dir=save_dir, config_dir=config_dir)\nexcept discovery_handoff.HandoffContractError as exc:\n    if \"Could not read\" in exc.message:\n        # permissions / IO: fix the store, then retry the leg\n        ...\n    raise","preventionTips":["Check ownership of handoff files when runs alternate between sudo and user contexts.","Keep the save dir on local, non-flaky storage.","Don't run concurrent discovery protocols against the same store."],"tags":["discovery","handoff","filesystem","permissions","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}