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 the

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Find the underlying FFI call that signaled failure without setting the exception and fix it (call PyErr_SetFromErrno or similar on failure)
  2. Reproduce in debug mode: the panic gives a precise backtrace to the offending call site
  3. 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

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


AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05). Data as JSON: /api/errors/13ab263b970c6f65. Report an issue: GitHub.