Hmbown/CodeWhale · error · RuntimeContractError
measurement emitted invalid JSON
Error message
measurement emitted invalid JSON: {error} What it means
After the measurement subprocess succeeds, run_measurement parses its stdout as JSON to obtain the receipt. If json.loads raises JSONDecodeError — the measurement printed warnings, logs, or partial output mixed with the JSON — the checker raises RuntimeContractError chaining the parse error. This isolates the contract so a malformed receipt never reaches validation.
Solutions
- Inspect the raw stdout shown in the JSONDecodeError context; remove stray non-JSON output (debug prints) from the measurement script.
- Re-run the measurement to rule out truncation or interruption.
- In measure-runtime-contract.py, send diagnostics to stderr and reserve stdout exclusively for the JSON document.
- Validate the script's output manually: `python3 scripts/measure-runtime-contract.py | python3 -m json.tool`.
Example fix
# before (measure script)
print("measuring...")
print(json.dumps(receipt))
# after
print("measuring...", file=sys.stderr)
print(json.dumps(receipt)) Defensive patterns
Strategy: type-guard
Validate before calling
import json raw = subprocess.run(["python3", "scripts/measure-runtime-contract.py"], capture_output=True, text=True).stdout json.loads(raw) # fails fast with position info before the checker runs
Type guard
def is_parseable_json(text: str) -> bool:
import json
try:
json.loads(text)
return True
except json.JSONDecodeError:
return False Try / catch
try:
receipt = json.loads(proc.stdout)
except json.JSONDecodeError as e:
print(f"non-JSON stdout at line {e.lineno} col {e.colno}: {e.msg}", file=sys.stderr)
print(proc.stdout[:500], file=sys.stderr) Prevention
- Keep measurement stdout strictly JSON; send logs to stderr.
- Pipe measurement output through python -m json.tool as a smoke test.
- Avoid debug print() statements in emitters; use logging to stderr.
When it happens
Trigger: measure-runtime-contract.py writes anything other than pure JSON to stdout (progress prints, cargo warnings captured into stdout, a panic message after exit 0 is impossible but partial writes on crash paths), producing unparseable stdout.
Common situations: A modified measure script added a print() for debugging; some environment hook or wrapper emitted banner text; the JSON was truncated by an interrupted run or pipe buffering issue.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Cannot parse portable data; use JSON for OpenCode or plain…
- <dynamic: wrapped serde_json parse error>
- measurement emitted invalid JSON
- measurement top level must be an object
- runtime-contract measurement failed with exit code
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/d5811f5eafd4ea60.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/check-runtime-contract-budget.py:418
env["CARGO_NET_OFFLINE"] = "true"
proc = subprocess.run(
[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_000View on GitHub (pinned to 433685b202)