PyO3/pyo3 · error

Attaching a thread to the interpreter is prohibited while a

Error message

Attaching a thread to the interpreter is prohibited while a __traverse__ implementation is running.

What it means

PyO3's AttachGuard::attach() panics with this message when a thread tries to attach to the Python interpreter while a __traverse__ (GC traversal) implementation is executing. PyO3 deliberately forbids attaching new threads during GC traversal because traversing may run arbitrary code that could resurrect objects or re-enter the collector. The ForbidAttaching guard sets an internal counter that makes try_attach fail with AttachError::ForbiddenDuringTraverse.

Source

Thrown at src/internal/state.rs:71

    ForbiddenDuringTraverse,
    /// The interpreter is not initialized.
    NotInitialized,
    #[cfg(Py_3_13)]
    /// The interpreter is finalizing.
    Finalizing,
}

impl AttachGuard {
    /// PyO3 internal API for attaching to the Python interpreter. The public API is Python::attach.
    ///
    /// If the thread was already attached via PyO3, this returns
    /// `AttachGuard::Assumed`. Otherwise, the thread will attach now and
    /// `AttachGuard::Ensured` will be returned.
    pub(crate) fn attach() -> Self {
        match Self::try_attach() {
            Ok(guard) => guard,
            Err(AttachError::ForbiddenDuringTraverse) => {
                panic!("{}", ForbidAttaching::FORBIDDEN_DURING_TRAVERSE)
            }
            Err(AttachError::NotInitialized) => {
                // try to initialize the interpreter and try again
                crate::interpreter_lifecycle::ensure_initialized();
                // SAFETY: just initialized the interpreter
                unsafe { Self::do_attach_unchecked() }
            }
            #[cfg(Py_3_13)]
            Err(AttachError::Finalizing) => {
                panic!("Cannot attach to the Python interpreter while it is finalizing.");
            }
        }
    }

    /// Variant of the above which will will return gracefully if the interpreter cannot be attached to.
    pub(crate) fn try_attach() -> Result<Self, AttachError> {
        match ATTACH_COUNT.try_with(|c| c.get()) {
            Ok(i) if i > 0 => {

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Remove any Python::attach/Python::with_gil calls from inside __traverse__; only touch raw pointers via the traverse visitor
  2. Move work that needs the GIL out of __traverse__ into drop, __clear__, or ordinary methods
  3. If you must run Rust code during traversal, keep it GIL-free (operate on borrowed data without calling PyO3 APIs that attach)
  4. Re-architect the pyclass to hold only raw pointers/simple data so traversal never triggers lazy init

Example fix

// before
unsafe fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
    let py = Python::with_gil(|py| py.clone()); // panics
    visit.call(&self.child)?; Ok(())
}
// after
unsafe fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
    if let Some(child) = &self.child { visit.call(child)?; } // no GIL attach
    Ok(())
}
Defensive patterns

Strategy: validation

Validate before calling

if pyo3::internal::state::is_in_gc_traversal() { /* skip attaching work */ } else { /* safe to attach */ }

Type guard

fn safe_to_attach() -> bool { !pyo3::internal::state::is_in_gc_traversal() }

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| pyo3::Python::with_gil(|py| work(py)))).unwrap_or_else(|_| { /* defer work */ })

Prevention

When it happens

Trigger: Calling Python::attach / Python::with_gil (or any API that internally attaches the thread) from within a __traverse__ implementation, e.g. inside unsafe fn __traverse__ of a #[pyclass] with gc protocol, or in a callback invoked during gc.collect().

Common situations: Implementing PyGCProtocol traverse for a pyclass holding references that trigger lazy initialization (which internally attaches the GIL); calling Python::with_gil inside a traverse visitor; dropping/lazily-initializing extension objects mid-traversal that re-enter PyO3 APIs.

Related errors


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