Hmbown/CodeWhale · error · PersistenceBacklogError

measurement receipt must be an object

Error message

measurement receipt must be an object

What it means

After successful JSON parsing, measure() requires the decoded receipt to be a dict/object. This error means the measurement command emitted valid JSON that is not an object (e.g. a list, string, or number), so it cannot be used as a receipt. It protects downstream field access into the receipt.

Solutions

  1. Update the measurement command to emit a top-level JSON object receipt
  2. Check the measurement tool version matches what the guard expects
  3. Inspect stdout of the command directly to confirm the emitted shape

Example fix

// before: print(json.dumps([r1, r2]))
// after:  print(json.dumps({"accepted_requests": ..., "applied_version": ..., ...}))
Defensive patterns

Strategy: type-guard

Type guard

def is_receipt(value: object) -> bool:
    return isinstance(value, dict) and all(k in value for k in ('accepted_requests', 'applied_version'))

Try / catch

receipt = json.loads(result.stdout)
if not isinstance(receipt, dict):
    raise PersistenceBacklogError(f"expected object receipt, got {type(receipt).__name__}")

Prevention

When it happens

Trigger: Running the budget check when the measurement command's stdout parses to a JSON value whose type is not dict — e.g. the tool prints a JSON array of samples or a bare string instead of a receipt object.

Common situations: Measurement tool version drift changing the output shape; a wrapper script printing a summary array; accidentally invoking a different command that emits non-object JSON.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/d2c3686debd9358b. Report an issue: GitHub.

Appendix: 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 433685b202)