Hmbown/CodeWhale · warning · io::Error
session was deleted
Error message
session was deleted
What it means
retired_session_write_error() constructs the canonical NotFound io::Error returned when a write path targets a session whose accounting was already retired (deleted). The admission check under the session's write lock sees the deleted marker and aborts the operation instead of resurrecting the files. Callers receive NotFound("session was deleted").
Solutions
- Treat io::ErrorKind::NotFound as an expected outcome: drop the pending write for that session instead of retrying.
- Re-check session existence before issuing writes and refresh your session list after deletions.
- Serialize deletes and writes through with_session_write_admission / with_live_session_origin so retirement is observed before the callback runs.
- If you need the data, restore the session from backup rather than recreating files by hand — manual recreation will not match the deletion marker.
Example fix
// before
persist_late_usage_record(&id, ...)?;
// after
if let Err(e) = persist_late_usage_record(&id, ...) {
if e.kind() == std::io::ErrorKind::NotFound { return Ok(()); } // session retired
return Err(e);
} Defensive patterns
Strategy: try-catch
Validate before calling
// before writing, confirm the session still exists
if !manager.session_exists(session_id) { return; } // skip write for retired session Type guard
fn is_session_retired(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::NotFound && err.to_string().contains("session was deleted")
} Try / catch
match manager.persist_late_usage_record(session_id, turn_id, source_id, ...) {
Ok(_) => {},
Err(e) if is_session_retired(&e) => { /* expected: session deleted concurrently, drop the write */ }
Err(e) => return Err(e),
} Prevention
- Route all writes through with_live_session_origin / with_session_write_admission so retirement is detected under the lock.
- Cancel in-flight turn bookkeeping when a session is deleted instead of letting writes race the deletion.
- Never retry NotFound write failures; retrying will not resurrect a deleted session.
When it happens
Trigger: Persisting or appending a late-usage record (or calling with_live_session_origin / persist_late_usage_record) for a session_id whose ledger/state files have been deleted concurrently or earlier by session cleanup.
Common situations: A background turn finishing after the user deleted the session; two tasks racing where one deletes the session while another still holds its id; retrying a queued write against a session removed during app shutdown.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- session accounting was deleted
- Agent not found
- Codewhale home directory not found
- invalid late usage deletion marker
- lane ` ` was stopped before tmux dry-run start completed
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8f93b6ae78fc26e7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1562
return Ok(None);
}
write().map(Some)
}
/// Serialize active accounting admission with deletion of its origin.
/// A retired origin is handled without running the callback. Callers must
/// release this boundary before attempting a late-usage append, which
/// independently checks retirement under the same stable lock.
pub(crate) fn with_live_session_origin(
&self,
session_id: &str,
accept: impl FnOnce() -> bool,
) -> io::Result<Option<bool>> {
self.with_session_write_admission(session_id, || Ok(accept()))
}
fn retired_session_write_error() -> io::Error {
io::Error::new(io::ErrorKind::NotFound, "session was deleted")
}
fn persist_late_usage_record(
&self,
session_id: &str,
turn_id: &str,
source_id: &str,
route: &crate::cost_status::EffectiveRouteEnvelope,
usage: Option<&codewhale_models::Usage>,
) -> io::Result<bool> {
let (path, lock_path) = self.ensure_late_usage_paths(session_id)?;
let lock_file = open_private_lock_file(&lock_path)?;
let mut lock = fd_lock::RwLock::new(lock_file);
let _guard = lock.write()?;
if Self::late_usage_is_deleted(&path)? {
// Handled, rather than a failed sink that should queue a retry.
return Ok(true);
}View on GitHub (pinned to 73e0f67d83)