mvanhorn/last30days-skill · error · HandoffContractError

Pending discovery report {path} must carry a top-level \"rep

Error message

Pending discovery report {path} must carry a top-level \"report\" object. {_RESUME_REMEDY}

What it means

`_parse_pending_file` raises HandoffContractError when the pending report's envelope is valid but its top-level `report` value is not an object. The `report` key carries the leg-2-authored discovery report that `--discover --finalize` stamps angles onto and emits; without a well-shaped report there is nothing to finalize. The remedy is the resume remedy: re-run leg 2, or restart the protocol if the bundle is also stale. Note `angle_inputs` is parsed leniently (missing/ill-shaped degrades to empty), but `report` is strict.

Source

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

            f"{_searched_lines(searched)}\n{_RESUME_REMEDY}"
        )
    return _parse_pending_file(path)


def _parse_pending_file(path: Path) -> PendingReport:
    payload, bundle_id, generated_at = _parse_handoff_envelope(
        path,
        label="Pending discovery report",
        kind=schema.DISCOVERY_PENDING_KIND,
        schema_version=schema.DISCOVERY_PENDING_SCHEMA_VERSION,
        remedy=_RESUME_REMEDY,
        missing_id_context="angles cannot bind to it",
        stale_context="the judged window it captured has moved on",
    )
    version = payload.get("schema_version")
    report = payload.get("report")
    if not isinstance(report, dict):
        raise HandoffContractError(
            f"Pending discovery report {path} must carry a top-level "
            f"\"report\" object. {_RESUME_REMEDY}"
        )
    # Lenient per row (engine-written, but one corrupt row must not discard
    # the rest): keep only well-shaped angle-input entries.
    angle_inputs_raw = payload.get("angle_inputs")
    angle_inputs = {
        str(nomination_id): {
            str(key): str(value) for key, value in info.items()
        }
        for nomination_id, info in (
            angle_inputs_raw.items() if isinstance(angle_inputs_raw, dict) else ()
        )
        if isinstance(info, dict)
    }
    return PendingReport(
        schema_version=str(version),
        bundle_id=bundle_id,

View on GitHub (pinned to c7460f6114)

Solutions

  1. Check `jq '.report | type' <path>` — must be 'object'.
  2. Re-run the resume leg (`--discover --judgments <file>`) to regenerate the pending report at full fidelity; hand-repair of the report object is not supported.
  3. Keep `--save-dir` consistent across legs 2 and 3 so the correct pending file is found.

Example fix

# before: jq 'del(.report)' accidentally applied to discover-pending.json
# after: re-run leg 2 in the same save dir
python3 last30days.py "topic" --discover --judgments judgments.json --save-dir /tmp/run
Defensive patterns

Strategy: type-guard

Validate before calling

import json
from pathlib import Path

def pending_has_report_object(path: Path) -> bool:
    try:
        return isinstance(json.loads(path.read_text(encoding="utf-8")).get("report"), dict)
    except (OSError, json.JSONDecodeError):
        return False

Type guard

def report_is_object(payload: dict) -> bool:
    return isinstance(payload.get("report"), dict)

Try / catch

from lib import discovery_handoff
try:
    pending = discovery_handoff.load_pending_report(save_dir=save_dir, config_dir=config_dir)
except discovery_handoff.HandoffContractError as exc:
    if '"report" object' in exc.message:
        # strict field: regenerate by re-running leg 2
        ...

Prevention

When it happens

Trigger: `discover-pending.json` edited so `report` became an array/string/null (e.g. `jq '.report |= [...]'` or `del(.report)` then re-adding wrong); a partially-written pending file from an interrupted leg 2; hand-construction of a pending report.

Common situations: Users trimming the pending file to 'reduce size'; sync-tool partial uploads; crash mid-write in leg 2; attempting to hand-forge leg-3 input.

Related errors


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