{"record":{"id":"8f93b6ae78fc26e7","repo":"Hmbown/CodeWhale","slug":"session-was-deleted","errorCode":null,"errorMessage":"session was deleted","messagePattern":"session was deleted","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/session_manager.rs","lineNumber":1562,"sourceCode":"            return Ok(None);\n        }\n        write().map(Some)\n    }\n\n    /// Serialize active accounting admission with deletion of its origin.\n    /// A retired origin is handled without running the callback. Callers must\n    /// release this boundary before attempting a late-usage append, which\n    /// independently checks retirement under the same stable lock.\n    pub(crate) fn with_live_session_origin(\n        &self,\n        session_id: &str,\n        accept: impl FnOnce() -> bool,\n    ) -> io::Result<Option<bool>> {\n        self.with_session_write_admission(session_id, || Ok(accept()))\n    }\n\n    fn retired_session_write_error() -> io::Error {\n        io::Error::new(io::ErrorKind::NotFound, \"session was deleted\")\n    }\n\n    fn persist_late_usage_record(\n        &self,\n        session_id: &str,\n        turn_id: &str,\n        source_id: &str,\n        route: &crate::cost_status::EffectiveRouteEnvelope,\n        usage: Option<&codewhale_models::Usage>,\n    ) -> io::Result<bool> {\n        let (path, lock_path) = self.ensure_late_usage_paths(session_id)?;\n        let lock_file = open_private_lock_file(&lock_path)?;\n        let mut lock = fd_lock::RwLock::new(lock_file);\n        let _guard = lock.write()?;\n        if Self::late_usage_is_deleted(&path)? {\n            // Handled, rather than a failed sink that should queue a retry.\n            return Ok(true);\n        }","sourceCodeStart":1544,"sourceCodeEnd":1580,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_manager.rs#L1544-L1580","documentation":"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\").","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npersist_late_usage_record(&id, ...)?;\n// after\nif let Err(e) = persist_late_usage_record(&id, ...) {\n    if e.kind() == std::io::ErrorKind::NotFound { return Ok(()); } // session retired\n    return Err(e);\n}","handlingStrategy":"try-catch","validationCode":"// before writing, confirm the session still exists\nif !manager.session_exists(session_id) { return; } // skip write for retired session","typeGuard":"fn is_session_retired(err: &std::io::Error) -> bool {\n    err.kind() == std::io::ErrorKind::NotFound && err.to_string().contains(\"session was deleted\")\n}","tryCatchPattern":"match manager.persist_late_usage_record(session_id, turn_id, source_id, ...) {\n    Ok(_) => {},\n    Err(e) if is_session_retired(&e) => { /* expected: session deleted concurrently, drop the write */ }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["not-found","race-condition","lifecycle","io","session-state"],"backgroundTag":"record-not-found","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}