Hmbown/CodeWhale · error · std::io::Error
(serde_json deserialization error wrapped as…
Error message
(serde_json deserialization error wrapped as io::ErrorKind::InvalidData)
What it means
read_evidence_metadata reads a JSON sidecar (the evidence artifact metadata file) for a given session/handle pair and deserializes it into EvidenceArtifact. When serde_json fails to parse or validate the file contents, the error is wrapped as an io::Error with ErrorKind::InvalidData. This means the file exists and was readable, but its bytes are not valid JSON for the EvidenceArtifact schema.
Solutions
- Inspect the metadata file at the resolved session artifact path and validate it as JSON (jq or serde_json::from_slice in a test) to see the exact serde message wrapped in the io error.
- Delete or restore the corrupt sidecar so the owning tool regenerates it on next evidence write.
- If schema drift is the cause, migrate or discard artifacts written by the older version rather than editing files in place.
- Wrap the call to treat InvalidData as 'artifact unreadable' and fall back to regenerating the evidence rather than failing the whole operation.
Example fix
// before
let artifact = read_evidence_metadata(&session_id, &handle)?;
// after
let artifact = match read_evidence_metadata(&session_id, &handle) {
Ok(a) => a,
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
// corrupt/legacy sidecar: regenerate or skip
regenerate_or_skip(&session_id, &handle)?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
fn evidence_metadata_readable(path: &std::path::Path) -> bool {
std::fs::read(path).map(|raw| serde_json::from_slice::<serde_json::Value>(&raw).is_ok()).unwrap_or(false)
} Type guard
fn is_valid_evidence_json(raw: &[u8]) -> bool {
serde_json::from_slice::<crate::tools::EvidenceArtifact>(raw).is_ok()
} Try / catch
match read_evidence_metadata(session_id, handle) {
Ok(a) => a,
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => fallback_regenerate(),
Err(e) => return Err(e),
} Prevention
- Only write evidence metadata via the owning tool's atomic write path, never by hand
- Check schema_version fields before parsing when versions may differ
- Treat InvalidData on artifact reads as a signal to regenerate, not to retry
When it happens
Trigger: Calling read_evidence_metadata(session_id, handle) where the metadata file at the session-scoped evidence path exists but contains corrupt, truncated, hand-edited, or schema-mismatched JSON (e.g. missing required fields of EvidenceArtifact).
Common situations: A previous write was interrupted mid-file; a user or script edited the sidecar by hand; the schema_version of EvidenceArtifact changed between app versions so old files no longer deserialize; disk corruption or a partially synced file.
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
- <serde_json deserialization error>
- (serde_json deserialization error wrapped as…
- deserialize
- <dynamic: wrapped serde_json parse error>
- <dynamic: wrapped serde_json serialization error>
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/5fdeda25c83c2b84.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/large_output_router.rs:314
pub fn publish_evidence_metadata(
session_id: &str,
artifact: &EvidenceArtifact,
) -> io::Result<PathBuf> {
let bytes = serde_json::to_vec_pretty(artifact)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
crate::artifacts::write_session_relative_immutable(
session_id,
&evidence_metadata_relative_path(&artifact.handle),
&bytes,
)
}
pub fn read_evidence_metadata(session_id: &str, handle: &str) -> io::Result<EvidenceArtifact> {
let relative = evidence_metadata_relative_path(handle);
let path = crate::artifacts::session_artifact_absolute_path(session_id, &relative)
.ok_or_else(|| io::Error::new(io::ErrorKind::PermissionDenied, "invalid evidence owner"))?;
let raw = std::fs::read(path)?;
serde_json::from_slice(&raw).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}
#[must_use]
pub fn unix_millis_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.try_into()
.unwrap_or(u64::MAX)
}
#[must_use]
pub fn evidence_is_expired(artifact: &EvidenceArtifact, now_ms: u64) -> bool {
artifact.retention_state == EvidenceRetentionState::Expired
|| now_ms > artifact.retain_until_unix_ms
}
View on GitHub (pinned to 433685b202)