mvanhorn/last30days-skill · error · HandoffContractError

Could not write nominations bundle {path}: {exc}

Error message

Could not write nominations bundle {path}: {exc}

What it means

`write_nominations_bundle` raises HandoffContractError when creating the state dir or writing `discover-nominations.json` raises OSError. The comment states the design intent: a locked, read-only, or full disk is the protocol's clean exit-2 path, never a traceback. The message includes the target path and the underlying OS error.

Source

Thrown at skills/last30days/scripts/lib/discovery_handoff.py:332

                list(enrichment_source_boundary)
                if enrichment_source_boundary is not None
                else None
            ),
            "requested_sources": (
                list(requested_sources) if requested_sources is not None else None
            ),
            "lookback_days": int(lookback_days),
        },
        "nominations": rows,
    }
    path = nominations_bundle_path(state_dir)
    try:
        state_dir.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    except OSError as exc:
        # A locked/read-only/full disk is the protocol's clean exit-2 path,
        # never a traceback.
        raise HandoffContractError(
            f"Could not write nominations bundle {path}: {exc}"
        ) from exc
    return NominationsBundle(
        schema_version=schema.DISCOVERY_NOMINATIONS_SCHEMA_VERSION,
        bundle_id=bundle_id,
        generated_at=generated_at,
        from_date=from_date,
        to_date=to_date,
        domain=domain,
        tier=tier,
        enrichment_source_boundary=(
            list(enrichment_source_boundary)
            if enrichment_source_boundary is not None
            else None
        ),
        requested_sources=(
            list(requested_sources) if requested_sources is not None else None
        ),

View on GitHub (pinned to c7460f6114)

Solutions

  1. Check the path named in the message: `ls -ld <dir>` and `df -h <dir>` for permission and space.
  2. Fix ownership/permissions (`chown -R "$USER" <dir>` or chmod u+w) or free disk space.
  3. Point `--save-dir` at a known-writable location for this run and subsequent legs.
  4. Remove a stale locked file at the bundle path if that is the blocker, then re-run the nominate-only sweep.

Example fix

# before: config dir owned by root
sudo mkdir -p ~/.config/last30days   # created as root
python3 last30days.py "topic" --discover --nominate-only
# after
sudo chown -R "$(id -un)" ~/.config/last30days && python3 last30days.py "topic" --discover --nominate-only
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path

def store_is_writable(state_dir: Path) -> bool:
    try:
        state_dir.mkdir(parents=True, exist_ok=True)
        probe = state_dir / ".write-probe"
        probe.write_text("x", encoding="utf-8")
        probe.unlink()
        return True
    except OSError:
        return False

Try / catch

from lib import discovery_handoff
try:
    bundle = discovery_handoff.write_nominations_bundle(...)
except discovery_handoff.HandoffContractError as exc:
    raise SystemExit(2) from exc  # clean exit; message names path + OS error

Prevention

When it happens

Trigger: `--save-dir` on a read-only mount; `~/.config/last30days/` owned by another user; disk full at write time; an immutable/locked file at the bundle path; SELinux/AppArmor denying writes.

Common situations: Shared servers where the config dir was created by root; macOS Full Disk Access blocking writes under certain protected paths; CI runners with small tmpfs; a leftover root-owned `discover-nominations.json` from a sudo run.

Related errors


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