Hmbown/CodeWhale · warning · io::Error

session accounting was deleted

Error message

session accounting was deleted

What it means

load_late_usage found the session's deletion marker on disk before reading the ledger, so it returns NotFound("session accounting was deleted") instead of loading a retired session's accounting. It distinguishes a deliberately deleted session from a merely missing file (which yields an empty/default ledger).

Solutions

  1. Handle ErrorKind::NotFound by treating the session's accounting as empty/absent and dropping references to the deleted session id.
  2. Refresh the session list after deletions so stale ids are not reused for reads.
  3. If the deletion was unintended, restore the session's accounting files from backup; do not just recreate empty files while the deleted marker persists.
  4. Route reads through load_late_usage rather than opening the ledger path directly so the deleted state is honored.

Example fix

// before
let ledger = manager.load_late_usage(&session_id)?;
// after
let ledger = match manager.load_late_usage(&session_id) {
    Ok(l) => l,
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => LateUsageLedger::default(), // deleted
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: fallback

Validate before calling

// before loading, check the session id is still known
if !known_sessions.contains(session_id) { return LateUsageLedger::default(); }

Type guard

fn is_accounting_deleted(err: &std::io::Error) -> bool {
    err.kind() == std::io::ErrorKind::NotFound && err.to_string().contains("session accounting was deleted")
}

Try / catch

let ledger = manager.load_late_usage(session_id).unwrap_or_else(|e| {
    debug_assert!(is_accounting_deleted(&e) || e.kind() == std::io::ErrorKind::NotFound);
    LateUsageLedger::default() // deleted session: treat as empty accounting
});

Prevention

When it happens

Trigger: Calling load_late_usage(session_id) after the session was deleted — late_usage_is_deleted(path) returned true under the read lock, indicating the tombstone/deleted state.

Common situations: A UI view or metrics aggregation still holds a stale session_id after the user deleted that session; restoring an old snapshot references a session whose accounting was removed.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/c9ff083da8349126. Report an issue: GitHub.

Appendix: source

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

        let lock_file = match open_private_read_file(&lock_path) {
            Ok(file) => file,
            Err(error) if error.kind() == io::ErrorKind::NotFound => {
                // Atomic replacement makes a copied ledger readable without
                // creating a lock. No writer can have published a tombstone
                // without first creating the stable lock.
                return read(&path);
            }
            Err(error) => return Err(error),
        };
        let lock = fd_lock::RwLock::new(lock_file);
        let _guard = lock.read()?;
        read(&path)
    }

    fn load_late_usage(&self, session_id: &str) -> io::Result<LateUsageLedger> {
        self.with_session_read_lock(session_id, |path| {
            if Self::late_usage_is_deleted(path)? {
                return Err(io::Error::new(
                    io::ErrorKind::NotFound,
                    "session accounting was deleted",
                ));
            }
            Self::load_late_usage_unlocked(path)
        })
    }

    fn apply_late_usage_to_metadata(&self, metadata: &mut SessionMetadata) {
        let ledger = match self.load_late_usage(&metadata.id) {
            Ok(ledger) => ledger,
            Err(_) => {
                // The transcript is independent of optional accounting data.
                // Keep a stable gap receipt even when this projection is later
                // saved; loading it again must not invent another missing call.
                let fingerprint = crate::cost_status::usage_source_fingerprint(&format!(
                    "late-usage-unavailable:{}",
                    crate::cost_status::usage_source_fingerprint(&metadata.id)

View on GitHub (pinned to 73e0f67d83)