PyO3/pyo3 · critical
attempted to fetch exception but none was set
Error message
attempted to fetch exception but none was set
What it means
failed_to_fetch is the fallback when pyo3 must produce a PyErr but Python's error indicator holds nothing (PyErr::take found no exception). In debug builds it panics; in release it returns a PySystemError carrying the same message.
Source
Thrown at src/err/mod.rs:664
#[inline]
fn from_state(state: PyErrState) -> PyErr {
PyErr { state }
}
#[inline]
fn normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
self.state.as_normalized(py)
}
}
/// Called when `PyErr::fetch` is called but no exception is set.
#[cold]
#[cfg_attr(debug_assertions, track_caller)]
fn failed_to_fetch() -> PyErr {
const FAILED_TO_FETCH: &str = "attempted to fetch exception but none was set";
if cfg!(debug_assertions) {
panic!("{}", FAILED_TO_FETCH)
} else {
crate::exceptions::PySystemError::new_err(FAILED_TO_FETCH)
}
}
impl core::fmt::Debug for PyErr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
Python::attach(|py| {
f.debug_struct("PyErr")
.field("type", &self.get_type(py))
.field("value", self.value(py))
.field(
"traceback",
&self.traceback(py).map(|tb| match tb.format() {
Ok(s) => s,
Err(err) => {
err.write_unraisable(py, Some(&tb));
// It would be nice to format what we can of theView on GitHub (pinned to ac9b6899d3)
Solutions
- Find the underlying FFI call that signaled failure without setting the exception and fix it (call PyErr_SetFromErrno or similar on failure)
- Reproduce in debug mode: the panic gives a precise backtrace to the offending call site
- Check for concurrent error-indicator clearing (other threads calling PyErr::clear/fetch) around the failing call
Example fix
// before: assumes any NULL return means an exception is set
let obj = NonNull::new(ffi_call()).ok()?; // then PyErr::take
// after: set an exception if none exists
let ptr = ffi_call();
if ptr.is_null() {
if PyErr::take(py).is_none() {
PyErr::new::<PySystemError, _>("call failed without exception").into();
}
return Err(...);
} Defensive patterns
Strategy: try-catch
Validate before calling
// after a failing FFI call, ensure an exception exists before fetching
if PyErr::take(py).is_none() {
PyErr::new::<pyo3::exceptions::PySystemError, _>("ffi call failed without setting an exception");
} Type guard
fn error_indicator_set(py: Python<'_>) -> bool { PyErr::take(py).is_some() } Try / catch
// run in debug builds so failed_to_fetch panics with a precise backtrace
#[cfg(debug_assertions)]
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe { ffi_call() })); Prevention
- Check every FFI return value and set a Python exception on failure yourself
- Avoid PyErr::take/fetch assumptions from non-Python threads
- Reproduce with debug_assertions enabled to get exact call sites
When it happens
Trigger: Calling an FFI function that should have set a Python exception (returned NULL/-1) but did not; calling PyErr::take or building PyErr from a null result when the GIL was acquired with no error set; misuse of unsafe APIs that assume an error indicator.
Common situations: Buggy C extension or libc call returning failure without setting an exception; race where another thread cleared the error indicator; pyo3 internal invariant violation during interpreter shutdown.
Related errors
- PyObject pointer is null
- Converting PyErr arguments failed: {}
- Unknown Py_GIL_DISABLED value
- invalid hex encoding
- `pyo3_build_config::get()` requires a direct dependency on `
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/13ab263b970c6f65.
Report an issue: GitHub.