Hmbown/CodeWhale · error · PersistenceBacklogError

measurement emitted invalid JSON: {error}

Error message

measurement emitted invalid JSON: {error}

What it means

The measurement subprocess exited 0 but its stdout was not parseable as JSON. The checker requires the child to print exactly one JSON receipt on stdout and nothing else, so any stray byte breaks parsing. The appended JSONDecodeError detail (line/column) tells you where stdout stops being valid JSON.

Source

Thrown at scripts/check-persistence-backlog-budget.py:390

def measure() -> dict[str, Any]:
    env = os.environ.copy()
    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,

View on GitHub (pinned to 8880682c63)

Solutions

  1. Run `python3 scripts/measure-persistence-backlog.py` and inspect stdout to find the non-JSON bytes
  2. Move any print/log statements in the measure script to stderr
  3. Redirect build and toolchain output to stderr inside the measure script
  4. Confirm purity with `python3 scripts/measure-persistence-backlog.py | python3 -m json.tool`

Example fix

# before (measure-persistence-backlog.py)
print("measuring...")
sys.stdout.write(json.dumps(receipt))
# after
print("measuring...", file=sys.stderr)
sys.stdout.write(json.dumps(receipt))
Defensive patterns

Strategy: try-catch

Validate before calling

out = subprocess.run([sys.executable, str(MEASURE_SCRIPT)], capture_output=True, text=True, check=False).stdout
json.loads(out)  # dry-run parse before wiring the child into the checker

Try / catch

try:
    receipt = json.loads(result.stdout)
except json.JSONDecodeError as e:
    show_stdout_context(result.stdout, e)
    raise

Prevention

When it happens

Trigger: A debug `print()` added to measure-persistence-backlog.py; cargo or rustc diagnostics routed to stdout; ANSI codes or log lines prepended to the receipt; output truncated mid-write.

Common situations: Temporary instrumentation added to the measure script without `file=sys.stderr`; a dependency printing to stdout on first run; platform differences in child-process output handling.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/d42b4e7c4549098c. Report an issue: GitHub.