{"record":{"id":"6a30a6ce8f9d47ea","repo":"openai/codex","slug":"mutex-poisoned","errorCode":null,"errorMessage":"mutex poisoned","messagePattern":"mutex poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"codex-rs/feedback/src/lib.rs","lineNumber":249,"sourceCode":"\n    /// Returns a [`tracing_subscriber`] layer that collects structured metadata for feedback.\n    ///\n    /// Events with `target: \"feedback_tags\"` are treated as key/value tags to attach to feedback\n    /// uploads later.\n    pub fn metadata_layer<S>(&self) -> impl Layer<S> + Send + Sync + 'static\n    where\n        S: tracing::Subscriber + for<'a> LookupSpan<'a>,\n    {\n        FeedbackMetadataLayer {\n            inner: self.inner.clone(),\n        }\n        .with_filter(Targets::new().with_target(FEEDBACK_TAGS_TARGET, Level::TRACE))\n    }\n\n    pub fn snapshot(&self, session_id: Option<ThreadId>) -> FeedbackSnapshot {\n        let bytes = {\n            #[allow(clippy::expect_used)]\n            let guard = self.inner.ring.lock().expect(\"mutex poisoned\");\n            guard.snapshot_bytes()\n        };\n        let tags = {\n            #[allow(clippy::expect_used)]\n            let guard = self.inner.tags.lock().expect(\"mutex poisoned\");\n            guard.clone()\n        };\n        FeedbackSnapshot {\n            bytes,\n            tags,\n            feedback_diagnostics: FeedbackDiagnostics::collect_from_env(),\n            thread_id: session_id\n                .map(|id| id.to_string())\n                .unwrap_or(\"no-active-thread-\".to_string() + &ThreadId::new().to_string()),\n        }\n    }\n}\n","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/feedback/src/lib.rs#L231-L267","documentation":"CodexFeedback::snapshot (codex-rs/feedback/src/lib.rs:246-265) locks two std::sync::Mutexes - the log ring buffer and the tags map - with .expect(\"mutex poisoned\"). A std Mutex becomes poisoned when a thread panics while holding it, and the same ring mutex is locked by the tracing writer path (FeedbackMakeWriter). So this panic means some earlier panic happened inside log writing while holding the lock; snapshot() merely detonates the leftover poison. The original panic appears earlier in the process output.","triggerScenarios":"Any panic on a thread holding inner.ring or inner.tags - typically inside the tracing-subscriber writer while formatting a log record into the ring buffer - after which every snapshot() call (feedback upload, tests) panics with 'mutex poisoned'.","commonSituations":"A formatting or ring-buffer bug panicking on a large or odd log line; test code panicking while the feedback subscriber is installed; any other panic in the process that happened mid-write. Restarting clears it - poison state is in-memory only.","solutions":["Find the FIRST panic in the process output - 'mutex poisoned' is the symptom; the earlier panic during log writing is the cause. Fix that","Restart the process to clear the poisoned lock - nothing on disk is broken","If you wrap this API, recover the lock with lock().unwrap_or_else(std::sync::PoisonError::into_inner) (the buffer contents are still readable) or wrap snapshot() in catch_unwind so feedback upload cannot take the host down"],"exampleFix":"// before\nlet snapshot = feedback.snapshot(session_id); // panics: mutex poisoned\n\n// after - feedback upload must never crash the host\nlet snapshot = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    feedback.snapshot(session_id)\n}))\n.ok()\n.inspect_err(|_| tracing::warn!(\"feedback snapshot unavailable (poisoned lock)\"));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let snapshot = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    feedback.snapshot(session_id)\n}));\nlet snapshot = match snapshot {\n    Ok(s) => Some(s),\n    Err(_) => {\n        tracing::warn!(\"feedback snapshot poisoned; skipping upload\");\n        None\n    }\n};","preventionTips":["Read backwards in the log for the FIRST panic - poison is always secondary","Keep tracing writer/MakeWriter code simple and total so it can never panic mid-write","Guard feedback upload with catch_unwind - diagnostics capture must not be load-bearing","A process restart clears the poison; no on-disk repair is needed"],"tags":["feedback","concurrency","mutex","panic","rust"],"backgroundTag":"mutex-poisoned","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}