PyO3/pyo3 · critical

Cannot clone pointer into Python heap without the thread bei

Error message

Cannot clone pointer into Python heap without the thread being attached.

What it means

Panic from Clone for a pointer type wrapping a Python heap object. Cloning bumps the CPython reference count via Py_INCREF, which is only legal while the calling thread is attached to the Python interpreter (holding the GIL or, on free-threaded builds, being attached). If the thread is not attached, incrementing the refcount would be unsynchronized/invalid, so the code aborts instead of corrupting the refcount.

Source

Thrown at src/instance.rs:2266

    #[inline]
    fn clone(&self) -> Self {
        #[track_caller]
        #[inline]
        fn try_incref(obj: NonNull<ffi::PyObject>) {
            use crate::internal::state::thread_is_attached;

            if thread_is_attached() {
                // SAFETY: Py_INCREF is safe to call on a valid Python object if the thread is attached.
                unsafe { ffi::Py_INCREF(obj.as_ptr()) }
            } else {
                incref_failed()
            }
        }

        #[cold]
        #[track_caller]
        fn incref_failed() -> ! {
            panic!("Cannot clone pointer into Python heap without the thread being attached.");
        }

        try_incref(self.0);

        Self(self.0, PhantomData)
    }
}

/// Dropping a `Py` instance decrements the reference count
/// on the object by one if the thread is attached to the Python interpreter.
///
/// Otherwise and by default, this registers the underlying pointer to have its reference count
/// decremented the next time PyO3 attaches to the Python interpreter.
///
/// However, if the `pyo3_disable_reference_pool` conditional compilation flag
/// is enabled, it will abort the process.
impl<T> Drop for Py<T> {
    #[inline]

View on GitHub (pinned to ac9b6899d3)

Solutions

  1. Wrap the clone in Python::with_gil(|py| ...) (or Python::attach) so the thread is attached first
  2. Don't share Py<T> across unattached threads; send via channels and clone inside with_gil
  3. Avoid touching Python objects during interpreter shutdown (use before-thread/atexit hooks to drop them earlier)

Example fix

// before (foreign thread)
let cloned = py_obj.clone(); // panics: not attached
// after
let cloned = Python::with_gil(|_py| py_obj.clone());
Defensive patterns

Strategy: type-guard

Validate before calling

fn can_touch_python() -> bool { Python::with_gil(|_| true) } // only call on threads you attach yourself

Type guard

fn is_attached() -> bool { Python::with_gil(|_py| /* reached means attached */ true) }

Try / catch

// guard foreign-thread access
std::panic::catch_unwind(|| Python::with_gil(|_py| py_obj.clone())).is_err()

Prevention

When it happens

Trigger: Calling .clone() on a Py<T>/Borrowed pointer from a thread that is not attached to Python (e.g. outside Python::with_gil, on a foreign thread without Python::attach/with_gil), or during interpreter finalization when attachment is refused.

Common situations: Rust threads spawned manually touching Py<T> clones without Python::with_gil; callbacks from non-Python threads; cloning during interpreter shutdown.

Related errors


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