{"record":{"id":"814d64d32fbaf5ab","repo":"tinyhumansai/openhuman","slug":"whatsapp-data-write-lock-poisoned-e","errorCode":null,"errorMessage":"whatsapp_data write lock poisoned: {e}","messagePattern":"whatsapp_data write lock poisoned: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src-tauri/src/whatsapp_data/store.rs","lineNumber":425,"sourceCode":"        let result: String = conn\n            .query_row(\"PRAGMA integrity_check(1)\", [], |row| row.get(0))\n            .context(\"running PRAGMA integrity_check\")?;\n        Ok(result.eq_ignore_ascii_case(\"ok\"))\n    }\n\n    /// Upsert chat metadata rows.  Returns the number of rows inserted or updated.\n    pub fn upsert_chats(\n        &self,\n        account_id: &str,\n        chats: &HashMap<String, ChatMeta>,\n    ) -> Result<usize> {\n        if chats.is_empty() {\n            return Ok(0);\n        }\n        let _write_guard = self\n            .write_lock\n            .lock()\n            .map_err(|e| anyhow::anyhow!(\"whatsapp_data write lock poisoned: {e}\"))?;\n        self.write_with_corrupt_recovery(\"upsert_chats\", || {\n            self.upsert_chats_inner(account_id, chats)\n        })\n    }\n\n    fn upsert_chats_inner(\n        &self,\n        account_id: &str,\n        chats: &HashMap<String, ChatMeta>,\n    ) -> Result<usize> {\n        let conn = self.open_conn()?;\n        let now = Self::now_secs();\n        let mut count = 0usize;\n        for (chat_id, meta) in chats {\n            let name = meta.name.as_deref().unwrap_or(\"\");\n            let is_group = chat_id.ends_with(\"@g.us\") as i64;\n            conn.execute(\n                \"INSERT INTO wa_chats (account_id, chat_id, display_name, is_group, updated_at)","sourceCodeStart":407,"sourceCodeEnd":443,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src-tauri/src/whatsapp_data/store.rs#L407-L443","documentation":"upsert_chats() acquires the store's write Mutex before entering write_with_corrupt_recovery; the lock is poisoned because another thread panicked while holding it, and .map_err converts the std::sync::PoisonError into this anyhow error. Every subsequent writer on this store instance fails identically until the process restarts — the poison is a symptom, the original panic is the disease.","triggerScenarios":"A panic inside any code holding write_lock (upsert_messages/prune/upsert_chats inner paths — e.g. an unwrap on a row invariant) poisons the mutex; the very next upsert_chats then fails here.","commonSituations":"A malformed chat map triggering an expect/unwrap in the write path; a rusqlite failure escalated to panic; code after error 294's corruption loop panicking mid-write.","solutions":["Restart the app — a poisoned mutex cannot be un-poisoned in-process","Find the original panic: search logs backwards for the panic message that precedes the first 'write lock poisoned' — fix that","Harden the write path: no unwrap/expect inside the critical section; validate inputs before taking the lock"],"exampleFix":"// before — panicking under the lock poisons it for every later writer\n let n = chats.len() as usize;\n self.write_with_corrupt_recovery(\"upsert_chats\", || self.upsert_chats_inner(account_id, chats))\n // ...inside inner: let name = meta.name.expect(\"always set\");  ← panics here\n\n// after — return errors instead of panicking inside the critical section\n let Some(name) = meta.name.clone() else {\n     anyhow::bail!(\"upsert_chats: chat {} missing name\", chat_id);\n };","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match store.upsert_chats(account_id, &chats) {\n    Ok(n) => log::info!(\"[whatsapp_data] upserted {n} chats\"),\n    Err(e) if e.to_string().contains(\"write lock poisoned\") => {\n        log::error!(\"[whatsapp_data] store poisoned — clean restart required; batch re-derivable\");\n        // do not retry in-process\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["never panic (unwrap/expect/index) while holding the write lock","log the first panic loudly so later poison errors are traceable to it","design ingestion to resume cleanly after an app restart"],"tags":["rust","mutex","concurrency","whatsapp","panic"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}