astrid-runtime/astrid · error
frozen SUMMARY deserializes
Error message
frozen SUMMARY deserializes
What it means
session_summary_round_trips_frozen_summary builds a full JSON object and deserializes it into SessionSummary with expect("frozen SUMMARY deserializes"). The test pins the frozen wire format of SessionSummary, so a panic means a serde field/type change broke backward compatibility with the frozen shape.
Source
Thrown at crates/astrid-gateway/src/routes/sessions_tests.rs:587
assert_eq!(p["include_archived"], false);
}
#[test]
fn session_summary_round_trips_frozen_summary() {
// The frozen SUMMARY shape, fully populated, then mostly-null.
let full = serde_json::json!({
"session_id": "default",
"title": "Planning",
"preview": "first user message",
"last_message_preview": "latest line",
"message_count": 12,
"created_at": 1_719_000_000_i64,
"updated_at": 1_719_000_100_i64,
"archived": false,
"parent_session_id": "old-id",
"meta": "{\"k\":1}"
});
let s: SessionSummary = serde_json::from_value(full).expect("frozen SUMMARY deserializes");
assert_eq!(s.session_id, "default");
assert_eq!(s.title.as_deref(), Some("Planning"));
assert_eq!(s.last_message_preview.as_deref(), Some("latest line"));
assert!(!s.archived);
assert_eq!(s.meta.as_deref(), Some("{\"k\":1}"));
// Minimal/sparse element: only the always-present fields.
let sparse = serde_json::json!({
"session_id": "fresh",
"message_count": 0,
"archived": true
});
let s: SessionSummary = serde_json::from_value(sparse).expect("sparse SUMMARY deserializes");
assert!(s.title.is_none());
assert!(s.preview.is_none());
assert!(s.last_message_preview.is_none());
assert!(s.created_at.is_none());
assert!(s.meta.is_none());View on GitHub (pinned to affd8760f4)
Solutions
- Revert the serde change to SessionSummary or update the frozen fixture deliberately with a documented schema change
- Make the new field #[serde(default)] if it must be optional for old payloads
- Run the test with the serde error message to identify the offending field
- If the format change is intended, update this round-trip test and any frozen-shape docs together
Example fix
// before pub meta: String, // after pub meta: Option<String>, // plus #[serde(default)] to keep frozen shape compatible
Defensive patterns
Strategy: try-catch
Validate before calling
assert!(full.get("session_id").map_or(false, |v| v.is_string()),
"frozen summary fixture malformed"); Type guard
fn has_required_summary(v: &serde_json::Value) -> bool {
["session_id", "message_count", "archived"]
.iter().all(|k| v.get(k).is_some())
} Try / catch
let s: SessionSummary = serde_json::from_value(full)
.unwrap_or_else(|e| panic!("frozen SUMMARY deserializes: {e}")); Prevention
- Keep SessionSummary fields Option/default so old payloads keep deserializing
- Update frozen fixtures only alongside a documented wire-format change
- Run the round-trip tests in CI on every serde attribute change
When it happens
Trigger: Changing SessionSummary fields or types (session_id, title, last_message_preview, message_count, created_at, updated_at, archived, parent_session_id, meta) so the full frozen JSON no longer deserializes.
Common situations: Renaming a field or making an optional field required; changing i64 timestamps to another type; adding a new required field absent from the frozen fixture; serde attribute changes (deny_unknown_fields, default removal).
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- sparse SUMMARY deserializes
- tool_describe payload is not valid JSON or has an unexpected
- reply deserializes
- frozen search shape deserializes
- durable capsule {id} has unsupported authority schema {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/689f3d930effadb1.
Report an issue: GitHub.