astrid-runtime/astrid · error
sparse SUMMARY deserializes
Error message
sparse SUMMARY deserializes
What it means
The same round-trip test also deserializes a minimal JSON object containing only session_id, message_count, and archived into SessionSummary with expect("sparse SUMMARY deserializes"). A panic means the always-present fields or their types changed, or a previously-optional field became required so the sparse payload no longer deserializes.
Source
Thrown at crates/astrid-gateway/src/routes/sessions_tests.rs:600
"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());
assert!(s.archived);
}
#[test]
fn parse_session_field_null_is_none_for_404() {
// Explicit null → None (the handler maps None to a 404).
let null_reply = serde_json::json!({ "correlation_id": "c", "session": null });
assert!(parse_session_field(&null_reply).unwrap().is_none());
// Absent `session` → None too.
let absent = serde_json::json!({ "correlation_id": "c" });
assert!(parse_session_field(&absent).unwrap().is_none());
}
View on GitHub (pinned to affd8760f4)
Solutions
- Restore Option/#[serde(default)] on fields that must remain absent-able in sparse responses
- Update the sparse fixture if a field legitimately joined the always-present set
- Read the serde error in the panic to find the missing/mistyped field
- Keep the frozen wire contract documented so field optionality changes are deliberate
Example fix
// before pub created_at: i64, // after #[serde(default)] pub created_at: Option<i64>,
Defensive patterns
Strategy: type-guard
Validate before calling
assert!(sparse.get("session_id").is_some() && sparse.get("title").is_none(),
"sparse fixture must omit optional fields"); Type guard
fn is_sparse_summary(v: &serde_json::Value) -> bool {
v.get("session_id").is_some()
&& v.get("message_count").is_some()
&& v.get("archived").is_some()
} Try / catch
let s: SessionSummary = serde_json::from_value(sparse)
.unwrap_or_else(|e| panic!("sparse SUMMARY deserializes: {e}")); Prevention
- Keep optional summary fields as Option with #[serde(default)]
- Never promote a field to required without updating the sparse fixture
- Review serde attribute diffs for optionality regressions
When it happens
Trigger: Making a field required that the sparse fixture omits (title, created_at, meta, etc.), changing types of session_id/message_count/archived, or removing #[serde(default)]/Option wrappers.
Common situations: Refactor tightening SessionSummary field optionality; switching archived from bool to an enum; changing message_count type; serde default attribute removed during cleanup.
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
- frozen 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/b433957dab5216fc.
Report an issue: GitHub.