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.

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.

Appendix: 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)