{"record":{"id":"6681f02ba6b0effe","repo":"PyO3/pyo3","slug":"pyerr-state-should-never-be-invalid-outside-of-nor","errorCode":null,"errorMessage":"PyErr state should never be invalid outside of normalization","messagePattern":"PyErr state should never be invalid outside of normalization","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/err/err_state.rs","lineNumber":65,"sourceCode":"                pvalue: args.arguments(py),\n            }\n        })))\n    }\n\n    pub(crate) fn normalized(normalized: PyErrStateNormalized) -> Self {\n        let state = Self::from_inner(PyErrStateInner::Normalized(normalized));\n        // This state is already normalized, by completing the Once immediately we avoid\n        // reaching the `py.detach` in `make_normalized` which is less efficient\n        // and introduces a GIL switch which could deadlock.\n        // See https://github.com/PyO3/pyo3/issues/4764\n        state.normalized.call_once(|| {});\n        state\n    }\n\n    pub(crate) fn restore(self, py: Python<'_>) {\n        self.inner\n            .into_inner()\n            .expect(\"PyErr state should never be invalid outside of normalization\")\n            .restore(py)\n    }\n\n    fn from_inner(inner: PyErrStateInner) -> Self {\n        Self {\n            normalized: Once::new(),\n            normalizing_thread: Cell::new(None),\n            inner: UnsafeCell::new(Some(inner)),\n        }\n    }\n\n    #[inline]\n    pub(crate) fn as_normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {\n        if self.normalized.is_completed() {\n            match unsafe {\n                // Safety: self.inner will never be written again once normalized.\n                &*self.inner.get()\n            } {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/src/err/err_state.rs#L47-L83","documentation":"`PyErrState::restore` unwraps the internal state, asserting that a `PyErr`'s state is always valid (normalized or lazily normalizable) outside of normalization itself. Panicking here means a `PyErr` with an invalid/empty state was restored into the Python interpreter. In practice this indicates a pyo3-internal invariant violation rather than a bug in user code.","triggerScenarios":"Manually constructing a `PyErr` with an invalid state and restoring it; calling `PyErr::restore` (or `PyErr::take`-style flows) with a state already consumed during normalization; unsafe FFI misuse of the PyErr API.","commonSituations":"Custom pyo3 extensions that build `PyErr` values from raw C-API results; pyo3 version mismatches where `PyErr` internals differ; use of deprecated/unsafe PyErr construction helpers.","solutions":["Construct errors only via safe APIs: `PyErr::new`, `PyErr::from_value`, `PyErr::from_type`","Do not restore a `PyErr` whose state was already consumed by normalization","Check for mixed pyo3 versions among extension modules (cargo tree)","Upgrade pyo3 and report the issue if it reproduces with only safe API usage"],"exampleFix":"// before\nlet err: PyErr = unsafe { /* built from invalid state */ }; err.restore(py)\n// after\nlet err = PyErr::new::<exceptions::PyValueError, _>(\"message\"); err.restore(py)","handlingStrategy":"validation","validationCode":"# Rust side: only build PyErr through safe constructors\nlet err = PyErr::new::<PyValueError, _>(\"msg\");","typeGuard":"fn has_valid_state(err: &PyErr) -> bool {\n    // safe API: PyErr is always validly constructed\n    !err.value(py).is_none()\n}","tryCatchPattern":"// Rust\nmatch result {\n    Ok(v) => v,\n    Err(e) => { e.restore(py); return Err(PySystemError::new_err(\"state error\")); }\n}","preventionTips":["Construct PyErr only via PyErr::new/from_value/from_type","Never restore a PyErr twice","Audit for mixed pyo3 versions with cargo tree","Report panics from safe-API-only usage upstream"],"tags":["rust","pyo3","pyerr","internal-invariant"],"backgroundTag":"pyerr-invalid-state","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}