Hmbown/CodeWhale · error · PersistenceBacklogError

measurement receipt must be an object

Error message

measurement receipt must be an object

What it means

The measurement child's stdout parsed as valid JSON but the top-level value is not an object (a list, string, or number). The checker requires a single JSON object carrying the receipt fields, so array-wrapped or scalar emission is a contract violation. The emitted value's type tells you which shape change happened in the measure script.

Source

Thrown at scripts/check-persistence-backlog-budget.py:392

    env["CARGO_NET_OFFLINE"] = "true"
    result = subprocess.run(
        [sys.executable, str(MEASURE_SCRIPT)],
        cwd=ROOT,
        env=env,
        text=True,
        capture_output=True,
        check=False,
    )
    sys.stderr.write(result.stderr)
    if result.returncode != 0:
        sys.stdout.write(result.stdout)
        raise PersistenceBacklogError("measurement command failed")
    try:
        receipt = json.loads(result.stdout)
    except json.JSONDecodeError as error:
        raise PersistenceBacklogError(f"measurement emitted invalid JSON: {error}") from error
    if not isinstance(receipt, dict):
        raise PersistenceBacklogError("measurement receipt must be an object")
    return receipt


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--receipt", type=Path, help="check an existing receipt")
    parser.add_argument("--budget", type=Path, default=BUDGET_PATH)
    args = parser.parse_args()
    try:
        expected_source = current_source_identity()
        receipt = load_json(args.receipt, "receipt") if args.receipt else measure()
        budget = load_json(args.budget, "budget")
        baseline_receipt = load_json(BASELINE_RECEIPT_PATH, "baseline receipt")
        validate_baseline_receipt(budget, baseline_receipt)
        increases, decreases = compare(
            receipt,
            budget,
            expected_source=expected_source,

View on GitHub (pinned to 8880682c63)

Solutions

  1. Check the emitted type: `python3 scripts/measure-persistence-backlog.py | python3 -c "import json,sys; print(type(json.load(sys.stdin)))"`
  2. Fix the script to emit exactly one receipt object (json.dumps(receipt), not a list)
  3. If multiple samples exist, select the canonical one before printing
  4. To validate a pre-existing artifact instead, pass `--receipt path.json` and bypass live measurement

Example fix

# before
sys.stdout.write(json.dumps(receipts))      # list
# after
sys.stdout.write(json.dumps(receipts[0]))  # single object
Defensive patterns

Strategy: type-guard

Validate before calling

value = json.loads(result.stdout)
assert isinstance(value, dict), f'receipt must be an object, got {type(value).__name__}'

Type guard

def is_receipt_object(value):
    return isinstance(value, dict) and isinstance(value.get('document_kind'), str)

Prevention

When it happens

Trigger: The measure script was changed to emit a list of per-sample receipts, a bare string/number, or a nested envelope; a different script ended up at scripts/measure-persistence-backlog.py.

Common situations: Extending the measure script for multi-sample runs without updating the checker; refactors that wrap the receipt for other consumers.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/d2c3686debd9358b. Report an issue: GitHub.