Hmbown/CodeWhale · error · io::Error

invalid late usage deletion marker

Error message

invalid late usage deletion marker

What it means

This io::Error with ErrorKind::InvalidData is raised when reading the tombstone file that marks a session as late-usage-deleted: the file's contents must exactly equal the marker bytes `codewhale-session-deleted-v1\n` (LATE_USAGE_DELETED). If the file holds anything else, the library refuses to trust the tombstone because a corrupted or hand-edited file could otherwise be mistaken for a valid deletion record. It surfaces from the session manager's deletion-marker validation path in crates/tui/src/session_manager.rs:1464.

Solutions

  1. Delete the corrupted tombstone file so the session is no longer marked deleted, then let Codewhale recreate state normally.
  2. Restore the file to the exact marker bytes `codewhale-session-deleted-v1\n` if it was accidentally modified (printf 'codewhale-session-deleted-v1\n' > tombstone).
  3. Check for version mismatch: if Codewhale was upgraded/downgraded, the marker constant may have changed; re-sync session files from the matching version or clear the stale tombstones.
  4. Verify disk health/free space — a partial write during the original atomic write is a common corruption source; re-run the affected session operation.

Example fix

// before: tombstone file edited externally, contains "codewhale-session-deleted-v1\n\n"
// after: restore exact marker bytes
$ printf 'codewhale-session-deleted-v1\n' > ~/.local/state/codewhale/sessions/<id>.tombstone
Defensive patterns

Strategy: validation

Validate before calling

let marker = std::fs::read(tombstone_path)?;
if marker != b"codewhale-session-deleted-v1\n" {
    // treat as corrupt: remove or restore the file before the library reads it
    let _ = std::fs::remove_file(tombstone_path);
}

Type guard

fn is_valid_tombstone(bytes: &[u8]) -> bool {
    bytes == b"codewhale-session-deleted-v1\n"
}

Try / catch

match manager.check_late_usage_tombstone(path) {
    Ok(deleted) => { /* proceed */ }
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        // corrupt marker: drop the tombstone and re-run
        let _ = std::fs::remove_file(path);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the session manager's late-usage tombstone check when the tombstone file exists but its bytes differ from LATE_USAGE_DELETED — e.g. a truncated read, a partial write, the marker version string changed between Codewhale versions, or the file was edited/overwritten externally (read path does `file.take(len+1).read_to_end` then compares to the constant).

Common situations: Upgrading/downgrading Codewhale so the marker version suffix (`-v1`) no longer matches; a crash or disk-full during a previous atomic write leaving partial content; manually inspecting or touching files under the sessions directory with an editor that adds a trailing newline, BOM, or rewrites bytes; copying session state between machines via a tool that mangles the marker.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        }
        Ok(paths)
    }

    /// A deletion marker and its stable lock survive deletion, without any
    /// route or usage data. A captured callback must never recreate the ledger.
    fn late_usage_is_deleted(path: &Path) -> io::Result<bool> {
        use std::io::Read as _;
        let tombstone = match open_private_read_file(&path.with_extension("deleted")) {
            Ok(file) => file,
            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
            Err(error) => return Err(error),
        };
        let mut marker = Vec::with_capacity(LATE_USAGE_DELETED.len());
        tombstone
            .take(u64::try_from(LATE_USAGE_DELETED.len()).unwrap_or(u64::MAX) + 1)
            .read_to_end(&mut marker)?;
        if marker != LATE_USAGE_DELETED {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "invalid late usage deletion marker",
            ));
        }
        Ok(true)
    }

    fn write_late_usage_ledger(path: &Path, ledger: &LateUsageLedger) -> io::Result<()> {
        let bytes = serde_json::to_vec(ledger)
            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
        if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > MAX_LATE_USAGE_LEDGER_BYTES {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "late usage ledger exceeds its size bound",
            ));
        }
        write_atomic(path, &bytes)
    }

View on GitHub (pinned to 73e0f67d83)