{"record":{"id":"14f89b70482aa895","repo":"PyO3/pyo3","slug":"cannot-clone-pointer-into-python-heap-without-the","errorCode":null,"errorMessage":"Cannot clone pointer into Python heap without the thread being attached.","messagePattern":"Cannot clone pointer into Python heap without the thread being attached\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/instance.rs","lineNumber":2266,"sourceCode":"    #[inline]\n    fn clone(&self) -> Self {\n        #[track_caller]\n        #[inline]\n        fn try_incref(obj: NonNull<ffi::PyObject>) {\n            use crate::internal::state::thread_is_attached;\n\n            if thread_is_attached() {\n                // SAFETY: Py_INCREF is safe to call on a valid Python object if the thread is attached.\n                unsafe { ffi::Py_INCREF(obj.as_ptr()) }\n            } else {\n                incref_failed()\n            }\n        }\n\n        #[cold]\n        #[track_caller]\n        fn incref_failed() -> ! {\n            panic!(\"Cannot clone pointer into Python heap without the thread being attached.\");\n        }\n\n        try_incref(self.0);\n\n        Self(self.0, PhantomData)\n    }\n}\n\n/// Dropping a `Py` instance decrements the reference count\n/// on the object by one if the thread is attached to the Python interpreter.\n///\n/// Otherwise and by default, this registers the underlying pointer to have its reference count\n/// decremented the next time PyO3 attaches to the Python interpreter.\n///\n/// However, if the `pyo3_disable_reference_pool` conditional compilation flag\n/// is enabled, it will abort the process.\nimpl<T> Drop for Py<T> {\n    #[inline]","sourceCodeStart":2248,"sourceCodeEnd":2284,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/src/instance.rs#L2248-L2284","documentation":"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.","triggerScenarios":"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.","commonSituations":"Rust threads spawned manually touching Py<T> clones without Python::with_gil; callbacks from non-Python threads; cloning during interpreter shutdown.","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)"],"exampleFix":"// before (foreign thread)\nlet cloned = py_obj.clone(); // panics: not attached\n// after\nlet cloned = Python::with_gil(|_py| py_obj.clone());","handlingStrategy":"type-guard","validationCode":"fn can_touch_python() -> bool { Python::with_gil(|_| true) } // only call on threads you attach yourself","typeGuard":"fn is_attached() -> bool { Python::with_gil(|_py| /* reached means attached */ true) }","tryCatchPattern":"// guard foreign-thread access\nstd::panic::catch_unwind(|| Python::with_gil(|_py| py_obj.clone())).is_err()","preventionTips":["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)"],"tags":["gil","thread-attach","py-pointer","panic"],"backgroundTag":"gil-not-held","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}