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

  1. Ensure the measurement emits the full receipt object starting with `{` (including kind/schema_version fields).
  2. Check the stdout in the error output — if it is `[...]`, update the emitter to wrap results in an object.
  3. Run the stock scripts/measure-runtime-contract.py rather than a modified/local variant.
  4. 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

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


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 * 1024

View on GitHub (pinned to 433685b202)