{"record":{"id":"d2c3686debd9358b","repo":"Hmbown/CodeWhale","slug":"measurement-receipt-must-be-an-object","errorCode":null,"errorMessage":"measurement receipt must be an object","messagePattern":"measurement receipt must be an object","errorType":"exception","errorClass":"PersistenceBacklogError","httpStatus":null,"severity":"error","filePath":"scripts/check-persistence-backlog-budget.py","lineNumber":392,"sourceCode":"    env[\"CARGO_NET_OFFLINE\"] = \"true\"\n    result = subprocess.run(\n        [sys.executable, str(MEASURE_SCRIPT)],\n        cwd=ROOT,\n        env=env,\n        text=True,\n        capture_output=True,\n        check=False,\n    )\n    sys.stderr.write(result.stderr)\n    if result.returncode != 0:\n        sys.stdout.write(result.stdout)\n        raise PersistenceBacklogError(\"measurement command failed\")\n    try:\n        receipt = json.loads(result.stdout)\n    except json.JSONDecodeError as error:\n        raise PersistenceBacklogError(f\"measurement emitted invalid JSON: {error}\") from error\n    if not isinstance(receipt, dict):\n        raise PersistenceBacklogError(\"measurement receipt must be an object\")\n    return receipt\n\n\ndef main() -> int:\n    parser = argparse.ArgumentParser(description=__doc__)\n    parser.add_argument(\"--receipt\", type=Path, help=\"check an existing receipt\")\n    parser.add_argument(\"--budget\", type=Path, default=BUDGET_PATH)\n    args = parser.parse_args()\n    try:\n        expected_source = current_source_identity()\n        receipt = load_json(args.receipt, \"receipt\") if args.receipt else measure()\n        budget = load_json(args.budget, \"budget\")\n        baseline_receipt = load_json(BASELINE_RECEIPT_PATH, \"baseline receipt\")\n        validate_baseline_receipt(budget, baseline_receipt)\n        increases, decreases = compare(\n            receipt,\n            budget,\n            expected_source=expected_source,","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/check-persistence-backlog-budget.py#L374-L410","documentation":"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.","triggerScenarios":"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.","commonSituations":"Extending the measure script for multi-sample runs without updating the checker; refactors that wrap the receipt for other consumers.","solutions":["Check the emitted type: `python3 scripts/measure-persistence-backlog.py | python3 -c \"import json,sys; print(type(json.load(sys.stdin)))\"`","Fix the script to emit exactly one receipt object (json.dumps(receipt), not a list)","If multiple samples exist, select the canonical one before printing","To validate a pre-existing artifact instead, pass `--receipt path.json` and bypass live measurement"],"exampleFix":"# before\nsys.stdout.write(json.dumps(receipts))      # list\n# after\nsys.stdout.write(json.dumps(receipts[0]))  # single object","handlingStrategy":"type-guard","validationCode":"value = json.loads(result.stdout)\nassert isinstance(value, dict), f'receipt must be an object, got {type(value).__name__}'","typeGuard":"def is_receipt_object(value):\n    return isinstance(value, dict) and isinstance(value.get('document_kind'), str)","tryCatchPattern":null,"preventionTips":["Emit exactly one JSON object from measure scripts","Assert the top-level type in the measure script's own tests","Use --receipt for pre-existing artifacts instead of the live measurement path"],"tags":["python","json","type-check","subprocess"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}