Hmbown/CodeWhale · error
{err}
Error message
{err} What it means
hydrate_approval_receipts loads durable approval receipts for a session and then replays them through ApprovalReplay::from_receipts; a replay failure is converted into io::ErrorKind::InvalidData. It means the stored receipt set is internally inconsistent (e.g. unmatched or malformed approval records) and cannot be reconstructed into a valid approval state. This fires during hydration for resume paths.
Solutions
- Read the chained source() error from ApprovalReplay to identify the malformed receipt
- Remove or repair the offending approval receipt entries in the session's receipt store
- Re-save the session so receipts are regenerated; or restore the receipt store from backup
- Check for version drift: receipts written by an older release may need migration
Defensive patterns
Strategy: try-catch
Validate before calling
ApprovalReplay::from_receipts(&session.approval_receipts).map_err(|e| format!("bad receipts: {e}"))?; Try / catch
match manager.hydrate_approval_receipts(&mut session) {
Ok(()) => resume(session),
Err(e) => {
log::warn!("receipt replay failed: {e}; starting without durable approvals");
session.approval_receipts.clear(); // fallback: drop corrupt receipts
}
} Prevention
- Never hand-edit or copy approval receipt stores between sessions
- Test receipt-store compatibility when upgrading across releases
- Back up the data directory before manual cleanup
When it happens
Trigger: Calling hydrate_approval_receipts when either approval_receipt_store().load succeeded but the receipts fail ApprovalReplay::from_receipts — corrupted, partial, or schema-drifted receipt entries in the store.
Common situations: Receipt files edited or truncated by a crash mid-write; receipts written by an older codewhale version whose format changed; manually copying approval receipt data between sessions.
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
- {error}
- Message dispatch belongs to a previous engine or session
- Message dispatch belongs to a previous session
- rendered body
- serialize
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/ad9bb6c612231686.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1254
/// Per-session offline queue file: `checkpoints/<session_id>.offline_queue.json`.
const OFFLINE_QUEUE_SUFFIX: &str = ".offline_queue.json";
pub(crate) fn is_offline_queue_file(name: &str) -> bool {
name == OFFLINE_QUEUE_FILE || name.ends_with(OFFLINE_QUEUE_SUFFIX)
}
impl SessionManager {
fn approval_receipt_store(&self) -> ApprovalReceiptStore {
ApprovalReceiptStore::new(self.sessions_dir.clone())
}
fn hydrate_approval_receipts(&self, session: &mut SavedSession) -> io::Result<()> {
let durable = self.approval_receipt_store().load(&session.metadata.id)?;
if !durable.is_empty() {
session.approval_receipts = durable;
}
ApprovalReplay::from_receipts(&session.approval_receipts)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
Ok(())
}
/// Reconstruct completed approvals and interrupted unmatched asks for one
/// session without consulting the model transcript.
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) fn replay_approvals(&self, session_id: &str) -> io::Result<ApprovalReplay> {
self.approval_receipt_store().replay(session_id)
}
fn validated_session_id<'a>(&self, id: &'a str) -> std::io::Result<&'a str> {
let trimmed = id.trim();
if trimmed.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Session id cannot be empty",
));
}View on GitHub (pinned to 73e0f67d83)