666ghj/MiroFish · error · StarHistoryError
history state must be a JSON object
Error message
history state must be a JSON object
What it means
validate_state requires the loaded history state to be a JSON object at the top level; anything else (list, string, number, null, bool) raises StarHistoryError('history state must be a JSON object'). This fires before _expect_keys so callers get a clear message rather than a KeyError on dict access.
Source
Thrown at scripts/star_history.py:346
normalized = _normalize_now(value)
return normalized.strftime("%Y-%m-%dT%H:%M:%SZ")
def _normalize_now(value: datetime) -> datetime:
if value.tzinfo is None or value.utcoffset() != timedelta(0):
raise StarHistoryError("clock must return a UTC datetime")
return value.astimezone(UTC).replace(microsecond=0)
def _expect_keys(value: Mapping[str, Any], expected: set[str], label: str) -> None:
actual = set(value)
if actual != expected:
raise StarHistoryError(f"{label} contains missing or unknown fields")
def validate_state(state: Any) -> None:
if not isinstance(state, dict):
raise StarHistoryError("history state must be a JSON object")
_expect_keys(
state,
{
"schema_version",
"repository",
"timezone",
"ongoing_interval_days",
"reconstruction",
"snapshots",
},
"history state",
)
if state["schema_version"] != 1 or type(state["schema_version"]) is not int:
raise StarHistoryError("unsupported history schema_version")
if state["repository"] != REPOSITORY:
raise StarHistoryError("history repository does not match configured repository")
if state["timezone"] != "UTC":
raise StarHistoryError("history timezone must be UTC")View on GitHub (pinned to b5b53acc57)
Solutions
- Inspect the first bytes of the state file to see what shape it actually is
- Regenerate the state file via the script's normal run path (full reconstruction from GitHub)
- If migrating from an old array format, write a one-off migration or discard and rebuild
Example fix
// before (state file)
[ {"date": "2024-01-01", "stars": 10} ]
// after (state file)
{ "schema_version": 1, "repository": "666ghj/MiroFish", "timezone": "UTC", "ongoing_interval_days": 13, "reconstruction": {"method": "...", "generated_at": "2024-01-15T10:30:00Z", "daily": []}, "snapshots": [] } Defensive patterns
Strategy: type-guard
Validate before calling
import json
with state_path.open() as fh:
state = json.load(fh)
if not isinstance(state, dict):
raise StarHistoryError(f"state file is a {type(state).__name__}, expected a JSON object") Type guard
def is_state_object(value: object) -> TypeGuard[dict]:
return isinstance(value, dict) Try / catch
try:
validate_state(state)
except StarHistoryError as exc:
if "must be a JSON object" in str(exc):
regenerate_state_file() # full reconstruction from GitHub
else:
raise Prevention
- Write state files atomically (temp file + rename) so truncation cannot leave partial JSON
- Version your state format with schema_version and migrate forward instead of keeping legacy shapes
When it happens
Trigger: A state file containing '[...]' (a JSON array, e.g. an old snapshots-only format), a bare string, or 'null'; also a truncated file that happens to decode to a scalar.
Common situations: State format changed across script versions (array -> object); the file was overwritten by another tool's output; a failed atomic write left partial JSON.
Related errors
- {label} must use YYYY-MM-DDTHH:MM:SSZ
- {label} is not a valid UTC timestamp
- {label} contains missing or unknown fields
- unsupported history schema_version
- history repository does not match configured repository
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/8409a264b976e2f3.
Report an issue: GitHub.