Hmbown/CodeWhale · error · std::io::Error

(serde_json serialization error wrapped as…

Error message

(serde_json serialization error wrapped as io::ErrorKind::InvalidData)

What it means

In `save_offline_queue_state`, the cloned `OfflineQueueState` is serialized with `serde_json::to_string_pretty` before an atomic write; any serialization failure is wrapped as an `io::ErrorKind::InvalidData` io::Error whose inner error is the serde error. In practice this is rare because the struct is JSON-safe, but a poisoned/invalid custom value or an out-of-range numeric field can trip it.

Solutions

  1. Inspect the inner serde error message (`{error}` source) to identify the offending field.
  2. Sanitize numeric fields (no NaN/Infinity) before saving.
  3. Ensure all nested types implement Serialize with string map keys.

Example fix

// before
state.retry_at = f64::NAN;
manager.save_offline_queue_state(&state, Some(id))?;
// after
if state.retry_at.is_finite() {
    manager.save_offline_queue_state(&state, Some(id))?;
}
Defensive patterns

Strategy: validation

Validate before calling

serde_json::to_string_pretty(&state).map_err(|e| e.to_string())?; // dry-run serialize before saving

Try / catch

match manager.save_offline_queue_state(&state, Some(id)) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
        eprintln!("queue state not serializable: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `save_offline_queue_state` with an `OfflineQueueState` that serde_json cannot serialize (non-string map keys in nested fields, NaN/Infinity floats, invalid values in custom Serialize impls).

Common situations: Custom fields added to the queue state containing NaN from computed timings; hand-built state structs populated from untrusted parsing output.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/5955eab235e8e662. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/session_manager.rs:2170

    pub fn save_offline_queue_state(
        &self,
        state: &OfflineQueueState,
        session_id: Option<&str>,
    ) -> std::io::Result<PathBuf> {
        let session_id = session_id.ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "Offline queue cannot be parked without a session id",
            )
        })?;
        let path = self.validated_offline_queue_path(session_id)?;
        fs::create_dir_all(self.checkpoints_dir())?;
        let mut owned = state.clone();
        // The stamp is redundant with the file name; it stays because the UI's
        // restore path still compares it against the live session id.
        owned.session_id = Some(self.validated_session_id(session_id)?.to_string());
        let content = serde_json::to_string_pretty(&owned)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
        write_atomic(&path, content.as_bytes())?;
        Ok(path)
    }

    /// Load one session's parked offline queue if present.
    pub fn load_offline_queue_state(
        &self,
        session_id: &str,
    ) -> std::io::Result<Option<OfflineQueueState>> {
        let path = self.validated_offline_queue_path(session_id)?;
        Ok(match Self::read_offline_queue_file(&path)? {
            Some(state) => Some(state),
            None => self.adopt_legacy_offline_queue(session_id, &path)?,
        })
    }

    /// Remove one named session's parked offline queue.
    pub fn clear_offline_queue_state_for(&self, session_id: &str) -> std::io::Result<()> {

View on GitHub (pinned to 433685b202)