Hmbown/CodeWhale · error · RuntimeContractError
measurement top level must be an object
Error message
measurement top level must be an object
What it means
run_measurement requires the parsed measurement output to be a JSON object (dict) at the top level, since the receipt schema is keyed (kind, schema_version, metrics, ...). If json.loads yields a list, string, number, or null, the checker raises RuntimeContractError before validate_receipt runs. This catches measurement scripts that emit a bare array or wrapper instead of the canonical receipt object.
Solutions
- Ensure the measurement emits the full receipt object starting with `{` (including kind/schema_version fields).
- Check the stdout in the error output — if it is `[...]`, update the emitter to wrap results in an object.
- Run the stock scripts/measure-runtime-contract.py rather than a modified/local variant.
- If a wrapper script must transform output, keep the top-level object intact and add fields instead.
Example fix
# before
print(json.dumps(metrics))
# after
print(json.dumps({"kind": "codewhale.runtime_contract_receipt", "schema_version": 1, **metrics})) Defensive patterns
Strategy: type-guard
Validate before calling
import json receipt = json.loads(proc.stdout) assert isinstance(receipt, dict), "measurement top level must be an object"
Type guard
def is_receipt(doc: object) -> bool:
return isinstance(doc, dict) and doc.get("kind") == "codewhale.runtime_contract_receipt" Try / catch
try:
validate_receipt(json.loads(proc.stdout))
except (json.JSONDecodeError, TypeError) as e:
print(f"unexpected measurement output shape: {e}", file=sys.stderr) Prevention
- Emit a dict with kind/schema_version keys, never a bare list or scalar.
- Pin the measurement script path; do not substitute local variants.
- Add a schema check right after json.loads in any wrapper tooling.
When it happens
Trigger: measure-runtime-contract.py (or a substitute script producing stdout) emits a JSON array, a bare value, or `null` instead of the receipt object.
Common situations: A rewritten or older measurement script emitting a different shape; piping in output of a different tool; accidentally substituting the MEASURE_SCRIPT or trimming wrapper code that wrapped the object.
Related errors
- Codewhale stream-json line
- credential file must be a JSON object of entries
- invalid : top level must be an object
- invalid MCP server definition list in key
- metric ` ` must be a non-negative integer
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/74abeca03f99212a.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/check-runtime-contract-budget.py:420
[sys.executable, str(MEASURE_SCRIPT)],
cwd=REPO_ROOT,
env=env,
capture_output=True,
text=True,
check=False,
)
sys.stderr.write(proc.stderr)
if proc.returncode != 0:
sys.stdout.write(proc.stdout)
raise RuntimeContractError(
f"runtime-contract measurement failed with exit code {proc.returncode}"
)
try:
receipt = json.loads(proc.stdout)
except json.JSONDecodeError as error:
raise RuntimeContractError(f"measurement emitted invalid JSON: {error}") from error
if not isinstance(receipt, dict):
raise RuntimeContractError("measurement top level must be an object")
validate_receipt(receipt)
return receipt
def update_command(receipt_path: Path | None, budget_path: Path) -> str:
parts = ["python3", "scripts/check-runtime-contract-budget.py"]
if receipt_path is not None:
parts.extend(["--receipt", str(receipt_path)])
if budget_path != BUDGET_PATH:
parts.extend(["--budget", str(budget_path)])
parts.append("--update")
return shlex.join(parts)
FRAGMENT_MODULE = REPO_ROOT / "crates" / "core" / "src" / "fragments.rs"
FRAGMENT_MAX_TOKENS_CEILING = 10_000
FRAGMENT_MAX_BYTES_CEILING = FRAGMENT_MAX_TOKENS_CEILING * 4
FRAGMENT_DEFAULT_MAX_BYTES_CEILING = 4 * 1024View on GitHub (pinned to 433685b202)