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
- Wrap the clone in Python::with_gil(|py| ...) (or Python::attach) so the thread is attached first
- Don't share Py<T> across unattached threads; send via channels and clone inside with_gil
- 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
- Clone Py<T> only inside Python::with_gil / Python::attach
- Never touch Python objects on unattached OS threads
- Drop Py<T> values before interpreter finalization (atexit/before-thread hooks)
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
- Attaching a thread to the interpreter is prohibited while a
- Cannot attach to the Python interpreter while it is finalizi
- Attaching a thread to the interpreter is currently prohibite
- Unknown Py_GIL_DISABLED value
- invalid hex encoding
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/14f89b70482aa895.
Report an issue: GitHub.