666ghj/MiroFish · error · StarHistoryError

snapshot must be an object

Error message

snapshot must be an object

What it means

Raised by validate_state() while iterating snapshots when an element is not a JSON object. Each snapshot must be a dict with exactly the keys 'at' (UTC timestamp string) and 'stars' (non-negative int), checked immediately after this guard.

Source

Thrown at scripts/star_history.py:430

        if index == 0 and stars <= 0:
            raise StarHistoryError("first reconstruction point must have stars")
        if previous_day is not None and point_day <= previous_day:
            raise StarHistoryError("reconstruction dates must be strictly increasing")
        if index > 0 and stars <= previous_stars:
            raise StarHistoryError("reconstruction stars must be strictly increasing")
        if point_day >= generated_at.date():
            raise StarHistoryError("reconstruction must contain only completed UTC dates")
        previous_day = point_day
        previous_stars = stars

    snapshots = state["snapshots"]
    if not isinstance(snapshots, list):
        raise StarHistoryError("snapshots must be a list")
    previous_snapshot: datetime | None = None
    first_snapshot: datetime | None = None
    for raw_snapshot in snapshots:
        if not isinstance(raw_snapshot, dict):
            raise StarHistoryError("snapshot must be an object")
        _expect_keys(raw_snapshot, {"at", "stars"}, "snapshot")
        snapshot_at = _parse_state_timestamp(raw_snapshot["at"], "snapshot.at")
        _strict_non_negative_int(raw_snapshot["stars"], "snapshot stars")
        if previous_snapshot is not None and snapshot_at <= previous_snapshot:
            raise StarHistoryError("snapshot timestamps must be strictly increasing")
        if first_snapshot is None:
            first_snapshot = snapshot_at
        previous_snapshot = snapshot_at

    if first_snapshot is not None:
        if first_snapshot < generated_at:
            raise StarHistoryError("first snapshot cannot predate reconstruction")
        if previous_day is not None and previous_day >= first_snapshot.date():
            raise StarHistoryError("reconstruction dates must predate snapshots")


def canonical_state_bytes(state: Mapping[str, Any]) -> bytes:
    validate_state(state)

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Emit each snapshot as an object with exactly 'at' and 'stars'
  2. Remove extra fields — the exact-key check rejects unknown fields too
  3. Regenerate the state file rather than patching entries

Example fix

// before
"snapshots": [["2024-01-02T00:00:00Z", 300]]
// after
"snapshots": [{"at": "2024-01-02T00:00:00Z", "stars": 300}]
Defensive patterns

Strategy: type-guard

Validate before calling

snaps = [
    {"at": s[0], "stars": s[1]} if isinstance(s, (list, tuple)) else s
    for s in snaps
]

Type guard

def are_snapshot_objects(snaps: list) -> bool:
    return all(isinstance(s, dict) and set(s) == {"at", "stars"} for s in snaps)

Try / catch

try:
    validate_state(state)
except StarHistoryError as exc:
    if "snapshot must be an object" in str(exc):
        # re-serialize tuple-form snapshots as objects, then re-validate
        ...

Prevention

When it happens

Trigger: validate_state(state) where a snapshot element is an array like ["2024-01-02T00:00:00Z", 300] or a bare integer.

Common situations: Serializing snapshots from tuple records; cross-language producers emitting arrays; hand-edited state files.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/e30a712f18503d4a. Report an issue: GitHub.