Hmbown/CodeWhale · error · RuntimeContractError

{kind} metric `{dotted}` must be a non-negative integer

Error message

{kind} metric `{dotted}` must be a non-negative integer

What it means

metric_value() enforces that every METRICS path (system-prompt bytes/tokens/blocks per mode, stage bytes and deltas, tool counts and schema bytes/tokens, skill-discovery deltas) is a non-negative integer, with bool explicitly rejected because bool is a subclass of int in Python. It runs inside compare() for both receipt and budget documents, and inside budget_from_receipt(). The message names the exact dotted path.

Source

Thrown at scripts/check-runtime-contract-budget.py:299

    representative = budget.get("representative_context")
    fixture_id = (
        representative.get("fixture_id")
        if isinstance(representative, dict)
        else None
    )
    if fixture_id != REPRESENTATIVE_FIXTURE_ID:
        raise RuntimeContractError(
            "budget metric `representative_context.fixture_id` must be "
            f"`{REPRESENTATIVE_FIXTURE_ID}`, got {fixture_id!r}"
        )
    validate_identity_structure(budget, "budget")


def metric_value(document: dict[str, Any], path: MetricPath, kind: str) -> int:
    value = required_value(document, path, kind)
    dotted = ".".join(path)
    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
        raise RuntimeContractError(
            f"{kind} metric `{dotted}` must be a non-negative integer"
        )
    return value


def compare(
    receipt: dict[str, Any], budget: dict[str, Any]
) -> tuple[list[MetricResult], list[MetricResult]]:
    """Return (increases, decreases) as path/label/current/ceiling tuples."""
    validate_receipt(receipt)
    validate_budget(budget)
    for path, label in IDENTITIES:
        receipt_value = required_value(receipt, path, "receipt")
        budget_value = required_value(budget, path, "budget")
        if receipt_value != budget_value:
            detail = ""
            if isinstance(receipt_value, list) and isinstance(budget_value, list):
                added = [str(item) for item in receipt_value if item not in budget_value]

View on GitHub (pinned to 8880682c63)

Solutions

  1. Rewrite the named metric as a plain non-negative JSON integer (no quotes, no decimal point, no sign)
  2. Sweep the whole file for the same defect: python3 -c "import json;d=json.load(open('scripts/runtime-contract-budget.json'));print([k for k,v in walk(d) if isinstance(v,float)])" style scan
  3. If the measure script is emitting floats, fix its serialization to int before re-measuring

Example fix

// before
"total_tokens_est": 2149.0
"system_prompt_bytes": "6084"

// after
"total_tokens_est": 2149
"system_prompt_bytes": 6084
Defensive patterns

Strategy: type-guard

Validate before calling

def all_metrics_are_ints(doc: dict) -> bool:
    stack = [doc]
    while stack:
        node = stack.pop()
        if isinstance(node, dict):
            stack.extend(node.values())
        elif isinstance(node, list):
            stack.extend(node)
        elif isinstance(node, float) or isinstance(node, bool):
            return False
    return True

Type guard

def is_non_negative_int(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool) and value >= 0

Prevention

When it happens

Trigger: A metric stored as a float (2149.0), a numeric string ("6084"), a negative number, or true/false - typically after hand-editing the budget JSON. Also a measure-script change that starts emitting float token estimates.

Common situations: Hand-tightening a ceiling in the budget and typing a decimal or string; JSON round-trips through tooling that coerces ints to floats (some YAML/JS pipelines); a receipt edited in a JSON editor that quotes numbers.

Related errors


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