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

`load_offline_queue_state` reads the parked queue JSON and deserializes it with `serde_json::from_str::<OfflineQueueState>`; a malformed file surfaces as `io::ErrorKind::InvalidData` wrapping the serde error. A missing file is handled separately and yields `Ok(None)`, so this error always means the file exists but is not valid JSON/does not match the struct.

Solutions

  1. Read the wrapped serde message to find the exact field/format mismatch.
  2. Delete the corrupted parked-queue file for that session (queue contents are lost) and let the session recreate it.
  3. Restore the file from backup if the queued input matters.
  4. Avoid hand-editing checkpoint files; let the app write them.
Defensive patterns

Strategy: try-catch

Validate before calling

if let Ok(txt) = std::fs::read_to_string(path) {
    if serde_json::from_str::<serde_json::Value>(&txt).is_err() {
        eprintln!("parked queue file is corrupt; delete or restore it");
    }
}

Try / catch

match manager.load_offline_queue_state(id) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
        // corrupt file: back it up, delete, continue without queued input
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `load_offline_queue_state` for a session whose parked-queue file is truncated, partially written, hand-edited, or has fields of the wrong type/shape.

Common situations: A crash mid-write on filesystems without atomic rename guarantees; manual edits to checkpoint files; a queue file written by an incompatible older format missing required fields.

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


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

Appendix: source

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

        }
        Ok(())
    }

    fn validated_offline_queue_path(&self, session_id: &str) -> std::io::Result<PathBuf> {
        let trimmed = self.validated_session_id(session_id)?;
        Ok(self
            .checkpoints_dir()
            .join(format!("{trimmed}{OFFLINE_QUEUE_SUFFIX}")))
    }

    fn read_offline_queue_file(path: &Path) -> std::io::Result<Option<OfflineQueueState>> {
        let content = match fs::read_to_string(path) {
            Ok(content) => content,
            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
            Err(error) => return Err(error),
        };
        let state: OfflineQueueState = serde_json::from_str(&content)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
        if state.schema_version > CURRENT_QUEUE_SCHEMA_VERSION {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "Offline queue schema v{} is newer than supported v{}",
                    state.schema_version, CURRENT_QUEUE_SCHEMA_VERSION
                ),
            ));
        }
        Ok(Some(state))
    }

    /// Migrate the pre-per-session global queue (`checkpoints/offline_queue.json`).
    ///
    /// It holds user-authored text, so it is adopted only by the session it was
    /// stamped for, and it is removed only once this session's copy is durably
    /// written. A queue stamped for someone else — or for nobody — is left
    /// exactly where it is, still readable, for its owner to claim.

View on GitHub (pinned to 433685b202)