mvanhorn/last30days-skill · error · HandoffContractError

Nominations bundle {path} contains no readable nominations (

Error message

Nominations bundle {path} contains no readable nominations (leg 1 never writes an empty pool). {_RESWEEP_REMEDY}

What it means

`_parse_bundle_file` raises HandoffContractError when the `nominations` array exists but yields zero readable rows — either literally empty or every row was skipped as malformed. The comment explains why this fails closed: leg 1 never writes an empty bundle (a zero-nomination sweep short-circuits without writing a file), so an empty/all-invalid pool is corrupt state, and silently handing the resume leg an empty pool would produce a meaningless run. The remedy is a fresh re-sweep.

Source

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

            )
            if isinstance(metrics, dict)
        }
        nominations.append(BundleNomination(
            nomination_id=str(row.get("id") or f"n{position}"),
            nomination=nomination,
            cluster_id=str(row.get("cluster_id") or ""),
            heuristic_name=str(row.get("heuristic_name") or ""),
            heuristic_junk=bool(row.get("heuristic_junk")),
            sources=[str(source) for source in row.get("sources") or []],
            engagement_by_source=engagement,
        ))

    if not nominations:
        # Leg 1 never writes an empty bundle (a zero-nomination sweep
        # short-circuits with no bundle file), so an empty or all-invalid
        # nominations array is corrupt state: fail closed, never hand the
        # resume leg a silently empty pool.
        raise HandoffContractError(
            f"Nominations bundle {path} contains no readable nominations "
            f"(leg 1 never writes an empty pool). {_RESWEEP_REMEDY}"
        )

    # Sweep status is advisory coverage context: restore it through the same
    # deserializer every report uses, but degrade a malformed map to empty
    # rather than discarding an otherwise-valid pool.
    try:
        source_status = schema._source_status_from_dict(payload)
    except (AttributeError, KeyError, TypeError, ValueError):
        _warn(f"ignoring malformed source_status map in {path.name}")
        source_status = {}

    return NominationsBundle(
        schema_version=str(version),
        bundle_id=bundle_id,
        generated_at=str(generated_at or ""),
        from_date=str(payload.get("from_date") or ""),

View on GitHub (pinned to c7460f6114)

Solutions

  1. Inspect `jq '.nominations | length' <path>` — 0 (or all-malformed rows) triggers this.
  2. If a filter emptied it, restore from the untouched bundle or re-run `--discover --nominate-only` and filter more conservatively.
  3. Never strip the pool to zero; if the sweep truly found nothing, the correct state is NO bundle file, which leg 2 reports differently (error 25).

Example fix

# before
jq '.nominations |= map(select(.worthiness == "high"))' discover-nominations.json > tmp && mv tmp discover-nominations.json  # filtered everything
# after: keep the pool intact; express junk exclusions in the judgments file, not by editing the bundle
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path

def pool_nonempty(path: Path) -> bool:
    try:
        rows = json.loads(path.read_text(encoding="utf-8")).get("nominations")
        return isinstance(rows, list) and any(isinstance(r, dict) for r in rows)
    except (OSError, json.JSONDecodeError):
        return False

Try / catch

from lib import discovery_handoff
try:
    bundle = discovery_handoff.load_nominations_bundle(save_dir=save_dir, config_dir=config_dir)
except discovery_handoff.HandoffContractError as exc:
    if "no readable nominations" in exc.message:
        # emptied pool: fail-closed by design; re-run the sweep
        ...

Prevention

When it happens

Trigger: `nominations: []` from a hand edit or bad jq filter that matched nothing (`map(select(...))` filtering everything out); every row individually non-dict (e.g. the array was replaced by strings), leaving zero after lenient skipping.

Common situations: A judgment-authoring step pre-filtering junk nominations too aggressively and emptying the pool; corruption that kept the array wrapper but destroyed rows; attempting to craft a minimal test bundle by hand.

Related errors


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