Hmbown/CodeWhale · error · io::Error
<dynamic: wrapped serde_json parse error>
Error message
<dynamic: wrapped serde_json parse error>
What it means
read_legacy_spillover_ownership reads a JSON sidecar describing legacy spillover ownership and deserializes it with serde_json. When the file's bytes are not valid JSON for the LegacySpilloverOwnership struct, the serde error is wrapped into an io::Error with ErrorKind::InvalidData. This surfaces as 'dynamic: wrapped serde_json parse error'.
Solutions
- Delete or fix the corrupt sidecar file so it parses as LegacySpilloverOwnership JSON
- Verify the sidecar contains a schema_version field matching LEGACY_SPILLOVER_OWNER_SCHEMA_VERSION
- Regenerate the sidecar by re-running the spillover migration/creation path
- Inspect the inner serde error message to identify the exact JSON field/type problem
Example fix
// before: hard failure on corrupt sidecar
let ownership = read_legacy_spillover_ownership(&sidecar)?;
// after: tolerate corrupt/absent legacy sidecar
let ownership = match read_legacy_spillover_ownership(&sidecar) {
Ok(o) => Some(o),
Err(e) if e.kind() == io::ErrorKind::InvalidData => None,
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check: is it plausible JSON with a schema_version?
let raw = std::fs::read_to_string(&sidecar)?;
if !raw.trim_start().starts_with('{') || !raw.contains("schema_version") {
// treat as corrupt/legacy; recreate instead of calling the reader
} Type guard
fn is_valid_sidecar(raw: &str) -> bool {
serde_json::from_str::<serde_json::Value>(raw)
.map(|v| v.get("schema_version").is_some())
.unwrap_or(false)
} Try / catch
match read_legacy_spillover_ownership(&sidecar) {
Ok(o) => o,
Err(e) if e.kind() == io::ErrorKind::InvalidData => recreate_sidecar(&sidecar)?,
Err(e) => return Err(e),
} Prevention
- Write the sidecar atomically (temp file + rename) so it is never seen truncated
- Validate sidecar JSON after every write
- Version-gate schema changes and ship migration code
- Never hand-edit sidecar files in place
When it happens
Trigger: Calling read_legacy_spillover_ownership on a sidecar file whose contents are malformed JSON, missing required fields of LegacySpilloverOwnership (e.g. schema_version), or contain wrong-typed values.
Common situations: A partially-written or truncated sidecar from a crash; a sidecar hand-edited to invalid JSON; a schema drift where an older version wrote different fields; a non-JSON file at the sidecar path.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- <serde_json deserialization error>
- (serde_json deserialization error wrapped as…
- (serde_json deserialization error wrapped as…
- (serde_json deserialization error wrapped as…
- (serde_json serialization error wrapped as…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f0a6b7c5ba978e43.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/truncate.rs:172
crate::utils::write_atomic(&sidecar, &encoded)?;
Ok(sidecar)
}
pub(crate) fn read_legacy_spillover_ownership(
payload_path: &Path,
) -> io::Result<LegacySpilloverOwnership> {
let sidecar = legacy_spillover_ownership_path(payload_path);
if std::fs::symlink_metadata(&sidecar)?
.file_type()
.is_symlink()
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"legacy spillover ownership sidecar must not be a symlink",
));
}
let ownership = serde_json::from_slice::<LegacySpilloverOwnership>(&std::fs::read(sidecar)?)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
if ownership.schema_version != LEGACY_SPILLOVER_OWNER_SCHEMA_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"unsupported legacy spillover ownership schema",
));
}
Ok(ownership)
}
/// Resolve the spillover-file path for a SHA256 content hash. Separate
/// namespace (`sha_<hex>.txt`) from the tool-call-id files so legacy
/// SHA-addressed evidence can be recognized without colliding with
/// tool-call references. Retrieval still requires matching ownership
/// metadata. `sha` must be the raw 64-char lowercase hex digest —
/// case-insensitive matching is done by the caller.
#[must_use]
pub fn sha_spillover_path(sha: &str) -> Option<PathBuf> {
let sha = sha.trim().to_ascii_lowercase();View on GitHub (pinned to 73e0f67d83)