{"record":{"id":"26c0b13539890c57","repo":"mvanhorn/last30days-skill","slug":"nominations-bundle-path-contains-no-readable-nom","errorCode":null,"errorMessage":"Nominations bundle {path} contains no readable nominations (leg 1 never writes an empty pool). {_RESWEEP_REMEDY}","messagePattern":"Nominations bundle (.+?) contains no readable nominations \\(leg 1 never writes an empty pool\\)\\. (.+?)","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":511,"sourceCode":"            )\n            if isinstance(metrics, dict)\n        }\n        nominations.append(BundleNomination(\n            nomination_id=str(row.get(\"id\") or f\"n{position}\"),\n            nomination=nomination,\n            cluster_id=str(row.get(\"cluster_id\") or \"\"),\n            heuristic_name=str(row.get(\"heuristic_name\") or \"\"),\n            heuristic_junk=bool(row.get(\"heuristic_junk\")),\n            sources=[str(source) for source in row.get(\"sources\") or []],\n            engagement_by_source=engagement,\n        ))\n\n    if not nominations:\n        # Leg 1 never writes an empty bundle (a zero-nomination sweep\n        # short-circuits with no bundle file), so an empty or all-invalid\n        # nominations array is corrupt state: fail closed, never hand the\n        # resume leg a silently empty pool.\n        raise HandoffContractError(\n            f\"Nominations bundle {path} contains no readable nominations \"\n            f\"(leg 1 never writes an empty pool). {_RESWEEP_REMEDY}\"\n        )\n\n    # Sweep status is advisory coverage context: restore it through the same\n    # deserializer every report uses, but degrade a malformed map to empty\n    # rather than discarding an otherwise-valid pool.\n    try:\n        source_status = schema._source_status_from_dict(payload)\n    except (AttributeError, KeyError, TypeError, ValueError):\n        _warn(f\"ignoring malformed source_status map in {path.name}\")\n        source_status = {}\n\n    return NominationsBundle(\n        schema_version=str(version),\n        bundle_id=bundle_id,\n        generated_at=str(generated_at or \"\"),\n        from_date=str(payload.get(\"from_date\") or \"\"),","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L493-L529","documentation":"`_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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Inspect `jq '.nominations | length' <path>` — 0 (or all-malformed rows) triggers this.","If a filter emptied it, restore from the untouched bundle or re-run `--discover --nominate-only` and filter more conservatively.","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)."],"exampleFix":"# before\njq '.nominations |= map(select(.worthiness == \"high\"))' discover-nominations.json > tmp && mv tmp discover-nominations.json  # filtered everything\n# after: keep the pool intact; express junk exclusions in the judgments file, not by editing the bundle","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\ndef pool_nonempty(path: Path) -> bool:\n    try:\n        rows = json.loads(path.read_text(encoding=\"utf-8\")).get(\"nominations\")\n        return isinstance(rows, list) and any(isinstance(r, dict) for r in rows)\n    except (OSError, json.JSONDecodeError):\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 \"no readable nominations\" in exc.message:\n        # emptied pool: fail-closed by design; re-run the sweep\n        ...","preventionTips":["Any jq filter over nominations must be checked with `jq '.nominations | length'` afterwards to confirm it kept rows.","A sweep with zero findings writes NO file — don't manufacture an empty one."],"tags":["discovery","handoff","fail-closed","json","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}