666ghj/MiroFish · error · StarHistoryError
snapshots must be a list
Error message
snapshots must be a list
What it means
Raised by validate_state() when the top-level 'snapshots' field of the state is not a JSON array. Snapshots are ongoing interval measurements (objects with 'at' and 'stars') appended over time; a non-list value fails before any snapshot is examined.
Source
Thrown at scripts/star_history.py:425
except ValueError as exc:
raise StarHistoryError("reconstruction date is invalid") from exc
if point_day.isoformat() != raw_date:
raise StarHistoryError("reconstruction date is not canonical")
stars = _strict_non_negative_int(raw_point["stars"], "reconstruction stars")
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():View on GitHub (pinned to b5b53acc57)
Solutions
- Make snapshots a JSON array of {"at": "YYYY-MM-DDTHH:MM:SSZ", "stars": <int>} objects (an empty array is valid)
- If snapshots were keyed by timestamp, convert to a list sorted by 'at'
- Regenerate state with the canonical writer
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 = state.get("snapshots")
if not isinstance(snaps, list):
state["snapshots"] = sorted(
[{"at": at, "stars": s} for at, s in snaps.items()],
key=lambda s: s["at"],
) if isinstance(snaps, dict) else [] Type guard
def is_snapshots_list(value) -> bool:
return isinstance(value, list) and all(
isinstance(s, dict) and set(s) == {"at", "stars"} for s in value
) Try / catch
try:
validate_state(state)
except StarHistoryError as exc:
if "snapshots must be a list" in str(exc):
# normalize to a list of {at, stars} objects, then re-validate
... Prevention
- Serialize snapshots as an array of objects
- Append new snapshots rather than rewriting the structure
- Validate state in CI when it is committed
When it happens
Trigger: validate_state(state) where state['snapshots'] is a dict, string, number, or null — e.g. keyed by timestamp instead of a list.
Common situations: Hand-editing or restructuring state; consuming state from a producer version with a different snapshots shape; JSON merge tools changing arrays to objects.
Related errors
- history state must be a JSON object
- reconstruction must be an object
- reconstruction.daily must be a list
- reconstruction point must be an object
- snapshot must be an object
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/49db4f5000baacb3.
Report an issue: GitHub.