Hmbown/CodeWhale · error · PersistenceBacklogError

{label} must be a JSON object

Error message

{label} must be a JSON object

What it means

After successfully parsing the budget or receipt JSON text, load_json requires the top-level value to be a JSON object (dict), not an array, string, number, or null. This error names which label ('budget'/'baseline receipt') failed the shape check before any field validation runs.

Source

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

    "rss_after_delta_bytes",
)
RSS_SAMPLE_FIELDS = ("rss_before_bytes", "rss_during_bytes", "rss_after_bytes")
RSS_DELTA_FIELDS = ("rss_during_delta_bytes", "rss_after_delta_bytes")
SUPPORTED_PLATFORMS = {"linux", "macos", "windows"}
SOURCE_SHA_PATTERN = re.compile(r"[0-9a-f]{40}")


class PersistenceBacklogError(ValueError):
    """A receipt or budget broke the measurement contract."""


def load_json(path: Path, label: str) -> dict[str, Any]:
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as error:
        raise PersistenceBacklogError(f"invalid {label} {path}: {error}") from error
    if not isinstance(value, dict):
        raise PersistenceBacklogError(f"{label} must be a JSON object")
    return value


def non_negative_integer(value: Any, field: str) -> int:
    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
        raise PersistenceBacklogError(f"{field} must be a non-negative integer")
    return value


def validate_frozen_field(field: str, value: Any, expected: Any) -> None:
    if type(value) is not type(expected) or value != expected:
        raise PersistenceBacklogError(
            f"receipt {field} must remain {expected!r}, got {value!r}"
        )


def current_source_identity() -> dict[str, Any]:
    def run(command: list[str]) -> str:

View on GitHub (pinned to 8880682c63)

Solutions

  1. Open the named file and wrap the top-level value in an object: {"entries": [...]} or the documented field names.
  2. Compare against the checked-in format used by the existing budget/receipt files in scripts/.
  3. If the array shape is the intended new format, update load_json and the downstream validators in the same change with tests.
  4. Re-run scripts/check-persistence-backlog-budget.py to confirm field-level validation proceeds.

Example fix

# before: scripts/persistence-backlog-budget.json
[{"max_files": 5000, "max_bytes": 1048576}]
# after
{"max_files": 5000, "max_bytes": 1048576}
Defensive patterns

Strategy: type-guard

Validate before calling

import json

def top_level_is_object(path) -> bool:
    return isinstance(json.loads(path.read_text(encoding="utf-8")), dict)

Type guard

def is_json_object(value: object) -> bool:
    return isinstance(value, dict)

Prevention

When it happens

Trigger: The budget file contains a JSON array of entries (e.g. '[{"max_files": ...}]'), a bare string/number, or 'null', all of which parse fine but are not objects. Only a {"...": ...} document at the top level is accepted.

Common situations: Hand-writing the budget as a list of rules; tools that emit NDJSON or a top-level array; an empty file left as 'null' by a serializer; refactoring the file format without updating the checker's expectation.

Related errors


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