diffstat for pybind11-3.0.1 pybind11-3.0.4

 .clang-tidy                                                    |    3 
 .codespell-ignore-lines                                        |    1 
 .github/CONTRIBUTING.md                                        |    2 
 .github/dependabot.yml                                         |    6 
 .github/workflows/ci.yml                                       |  322 +++--
 .github/workflows/configure.yml                                |   14 
 .github/workflows/docs-link.yml                                |    2 
 .github/workflows/format.yml                                   |    6 
 .github/workflows/labeler.yml                                  |    2 
 .github/workflows/nightlies.yml                                |   10 
 .github/workflows/pip.yml                                      |   20 
 .github/workflows/reusable-standard.yml                        |   10 
 .github/workflows/tests-cibw.yml                               |   29 
 .github/workflows/upstream.yml                                 |    8 
 .gitignore                                                     |    1 
 .pre-commit-config.yaml                                        |   31 
 CMakeLists.txt                                                 |    4 
 README.rst                                                     |   31 
 debian/changelog                                               |    9 
 debian/control                                                 |    1 
 docs/advanced/classes.rst                                      |   48 
 docs/advanced/embedding.rst                                    |    8 
 docs/advanced/functions.rst                                    |    4 
 docs/basics.rst                                                |    2 
 docs/changelog.md                                              |  258 ++++
 docs/compiling.rst                                             |   10 
 docs/requirements.txt                                          |   10 
 docs/upgrade.rst                                               |    2 
 include/pybind11/attr.h                                        |   14 
 include/pybind11/buffer_info.h                                 |    7 
 include/pybind11/cast.h                                        |  377 +++---
 include/pybind11/chrono.h                                      |    3 
 include/pybind11/complex.h                                     |   23 
 include/pybind11/detail/argument_vector.h                      |  417 ++++++
 include/pybind11/detail/class.h                                |   69 -
 include/pybind11/detail/common.h                               |  139 +-
 include/pybind11/detail/cpp_conduit.h                          |    4 
 include/pybind11/detail/function_record_pyobject.h             |    5 
 include/pybind11/detail/holder_caster_foreign_helpers.h        |  104 +
 include/pybind11/detail/init.h                                 |   52 
 include/pybind11/detail/internals.h                            |  483 ++++++-
 include/pybind11/detail/native_enum_data.h                     |   40 
 include/pybind11/detail/pybind11_namespace_macros.h            |   12 
 include/pybind11/detail/struct_smart_holder.h                  |   42 
 include/pybind11/detail/type_caster_base.h                     |  356 +++--
 include/pybind11/detail/value_and_holder.h                     |    6 
 include/pybind11/eigen/tensor.h                                |    7 
 include/pybind11/embed.h                                       |   14 
 include/pybind11/gil.h                                         |    4 
 include/pybind11/gil_safe_call_once.h                          |  193 ++-
 include/pybind11/iostream.h                                    |   44 
 include/pybind11/native_enum.h                                 |   13 
 include/pybind11/numpy.h                                       |  104 +
 include/pybind11/pybind11.h                                    |  611 +++++++---
 include/pybind11/pytypes.h                                     |  106 +
 include/pybind11/stl/filesystem.h                              |    2 
 include/pybind11/stl_bind.h                                    |    2 
 include/pybind11/subinterpreter.h                              |   38 
 include/pybind11/typing.h                                      |    5 
 pyproject.toml                                                 |   35 
 tests/CMakeLists.txt                                           |   80 -
 tests/conftest.py                                              |   54 
 tests/env.py                                                   |   67 -
 tests/extra_python_package/test_files.py                       |    2 
 tests/local_bindings.h                                         |   15 
 tests/mod_per_interpreter_gil_with_singleton.cpp               |  147 ++
 tests/pure_cpp/CMakeLists.txt                                  |    4 
 tests/pybind11_cross_module_tests.cpp                          |   16 
 tests/pybind11_tests.cpp                                       |   12 
 tests/pyproject.toml                                           |    4 
 tests/pytest.ini                                               |    2 
 tests/requirements.txt                                         |    6 
 tests/standalone_enum_module.cpp                               |   13 
 tests/test_buffers.cpp                                         |  135 ++
 tests/test_buffers.py                                          |   72 +
 tests/test_builtin_casters.cpp                                 |    4 
 tests/test_builtin_casters.py                                  |   85 +
 tests/test_callbacks.py                                        |   13 
 tests/test_class.cpp                                           |   19 
 tests/test_class.py                                            |   22 
 tests/test_class_cross_module_use_after_one_module_dealloc.cpp |   23 
 tests/test_class_cross_module_use_after_one_module_dealloc.py  |   43 
 tests/test_class_release_gil_before_calling_cpp_dtor.cpp       |    1 
 tests/test_class_sh_basic.py                                   |    4 
 tests/test_class_sh_mi_thunks.cpp                              |  137 ++
 tests/test_class_sh_mi_thunks.py                               |   51 
 tests/test_class_sh_property.cpp                               |   35 
 tests/test_class_sh_property.py                                |   52 
 tests/test_class_sh_property_non_owning.cpp                    |    2 
 tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp          |    6 
 tests/test_cmake_build/installed_embed/CMakeLists.txt          |    2 
 tests/test_cmake_build/installed_function/CMakeLists.txt       |    2 
 tests/test_cmake_build/installed_target/CMakeLists.txt         |    2 
 tests/test_cmake_build/subdirectory_embed/CMakeLists.txt       |    8 
 tests/test_cmake_build/subdirectory_function/CMakeLists.txt    |    2 
 tests/test_cmake_build/subdirectory_target/CMakeLists.txt      |    2 
 tests/test_cpp_conduit.py                                      |    2 
 tests/test_cross_module_rtti/CMakeLists.txt                    |    4 
 tests/test_cross_module_rtti/lib.h                             |    1 
 tests/test_custom_type_casters.py                              |    2 
 tests/test_custom_type_setup.cpp                               |   73 +
 tests/test_custom_type_setup.py                                |   41 
 tests/test_docstring_options.py                                |    8 
 tests/test_eigen_matrix.cpp                                    |    1 
 tests/test_embed/CMakeLists.txt                                |   61 
 tests/test_embed/catch.cpp                                     |   43 
 tests/test_embed/external_module.cpp                           |   24 
 tests/test_embed/test_interpreter.cpp                          |  482 -------
 tests/test_embed/test_interpreter.py                           |   16 
 tests/test_embed/test_subinterpreter.cpp                       |  442 -------
 tests/test_embed/test_trampoline.py                            |   18 
 tests/test_enum.py                                             |    2 
 tests/test_exceptions.py                                       |    4 
 tests/test_factory_constructors.cpp                            |    8 
 tests/test_factory_constructors.py                             |    8 
 tests/test_gil_scoped.py                                       |   13 
 tests/test_iostream.cpp                                        |   36 
 tests/test_iostream.py                                         |   42 
 tests/test_kwargs_and_defaults.py                              |   56 
 tests/test_local_bindings.cpp                                  |   25 
 tests/test_local_bindings.py                                   |   34 
 tests/test_methods_and_attributes.cpp                          |  296 ++++
 tests/test_methods_and_attributes.py                           |  163 ++
 tests/test_multiple_interpreters.py                            |  283 ++++
 tests/test_native_enum.cpp                                     |   12 
 tests/test_native_enum.py                                      |   57 
 tests/test_numpy_array.cpp                                     |   26 
 tests/test_numpy_array.py                                      |   39 
 tests/test_numpy_dtypes.py                                     |    2 
 tests/test_numpy_vectorize.cpp                                 |   30 
 tests/test_numpy_vectorize.py                                  |   55 
 tests/test_potentially_slicing_weak_ptr.cpp                    |    2 
 tests/test_pytorch_shared_ptr_cast_regression.cpp              |   62 +
 tests/test_pytorch_shared_ptr_cast_regression.py               |   25 
 tests/test_pytypes.cpp                                         |    6 
 tests/test_pytypes.py                                          |   55 
 tests/test_scoped_critical_section.cpp                         |    7 
 tests/test_sequences_and_iterators.cpp                         |    2 
 tests/test_smart_ptr.cpp                                       |   46 
 tests/test_smart_ptr.py                                        |   13 
 tests/test_standalone_enum_module.py                           |   18 
 tests/test_stl.cpp                                             |    1 
 tests/test_stl.py                                              |   22 
 tests/test_stl_binders.cpp                                     |    4 
 tests/test_stl_binders.py                                      |    6 
 tests/test_tagbased_polymorphic.cpp                            |    2 
 tests/test_thread.cpp                                          |   38 
 tests/test_thread.py                                           |   12 
 tests/test_type_caster_pyobject_ptr.py                         |    3 
 tests/test_unnamed_namespace_a.py                              |    5 
 tests/test_virtual_functions.cpp                               |    3 
 tests/test_with_catch/CMakeLists.txt                           |   64 +
 tests/test_with_catch/catch.cpp                                |  175 ++
 tests/test_with_catch/catch_skip.h                             |   21 
 tests/test_with_catch/external_module.cpp                      |   39 
 tests/test_with_catch/test_args_convert_vector.cpp             |   80 +
 tests/test_with_catch/test_argument_vector.cpp                 |   94 +
 tests/test_with_catch/test_interpreter.cpp                     |  511 ++++++++
 tests/test_with_catch/test_interpreter.py                      |   16 
 tests/test_with_catch/test_subinterpreter.cpp                  |  579 +++++++++
 tests/test_with_catch/test_trampoline.py                       |   18 
 tools/pybind11Common.cmake                                     |   13 
 tools/pybind11GuessPythonExtSuffix.cmake                       |   32 
 tools/pybind11NewTools.cmake                                   |   26 
 tools/test-pybind11GuessPythonExtSuffix.cmake                  |   26 
 165 files changed, 7458 insertions(+), 2306 deletions(-)

diff -Nru pybind11-3.0.1/.clang-tidy pybind11-3.0.4/.clang-tidy
--- pybind11-3.0.1/.clang-tidy	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.clang-tidy	2026-04-19 06:01:51.000000000 +0300
@@ -48,7 +48,10 @@
   readability-misplaced-array-index,
   readability-non-const-parameter,
   readability-qualified-auto,
+  readability-redundant-casting,
   readability-redundant-function-ptr-dereference,
+  readability-redundant-inline-specifier,
+  readability-redundant-member-init,
   readability-redundant-smartptr-get,
   readability-redundant-string-cstr,
   readability-simplify-subscript-expr,
diff -Nru pybind11-3.0.1/CMakeLists.txt pybind11-3.0.4/CMakeLists.txt
--- pybind11-3.0.1/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -10,7 +10,7 @@
   cmake_policy(GET CMP0148 _pybind11_cmp0148)
 endif()
 
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 if(_pybind11_cmp0148)
   cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
@@ -180,6 +180,7 @@
 endif()
 
 set(PYBIND11_HEADERS
+    include/pybind11/detail/argument_vector.h
     include/pybind11/detail/class.h
     include/pybind11/detail/common.h
     include/pybind11/detail/cpp_conduit.h
@@ -187,6 +188,7 @@
     include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
     include/pybind11/detail/exception_translation.h
     include/pybind11/detail/function_record_pyobject.h
+    include/pybind11/detail/holder_caster_foreign_helpers.h
     include/pybind11/detail/init.h
     include/pybind11/detail/internals.h
     include/pybind11/detail/native_enum_data.h
diff -Nru pybind11-3.0.1/.codespell-ignore-lines pybind11-3.0.4/.codespell-ignore-lines
--- pybind11-3.0.1/.codespell-ignore-lines	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.codespell-ignore-lines	2026-04-19 06:01:51.000000000 +0300
@@ -2,6 +2,7 @@
     template <typename ThisT>
         auto &this_ = static_cast<ThisT &>(*this);
                 if (load_impl<ThisT>(temp, false)) {
+                return load_impl<ThisT>(src, false);
         ssize_t nd = 0;
         auto trivial = broadcast(buffers, nd, shape);
         auto ndim = (size_t) nd;
diff -Nru pybind11-3.0.1/debian/changelog pybind11-3.0.4/debian/changelog
--- pybind11-3.0.1/debian/changelog	2025-12-25 00:14:53.000000000 +0200
+++ pybind11-3.0.4/debian/changelog	2026-09-07 21:26:00.000000000 +0300
@@ -1,3 +1,12 @@
+pybind11 (3.0.4-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * New upstream release.
+    - Fixes crashes during interpreter shutdown. (Closes: #1144807)
+  * Update the Uploaders list. (Closes: #1138110)
+
+ -- Adrian Bunk <bunk@debian.org>  Mon, 07 Sep 2026 21:26:00 +0300
+
 pybind11 (3.0.1-3) unstable; urgency=medium
 
   * Team upload.
diff -Nru pybind11-3.0.1/debian/control pybind11-3.0.4/debian/control
--- pybind11-3.0.1/debian/control	2025-12-25 00:14:53.000000000 +0200
+++ pybind11-3.0.4/debian/control	2026-09-07 21:26:00.000000000 +0300
@@ -2,7 +2,6 @@
 Standards-Version: 4.7.2
 Maintainer: Debian Science Maintainers <debian-science-maintainers@lists.alioth.debian.org>
 Uploaders:
- Ghislain Antony Vaillant <ghisvail@gmail.com>,
  Emmanuel Arias <eamanu@debian.org>,
 Section: libs
 Priority: optional
diff -Nru pybind11-3.0.1/docs/advanced/classes.rst pybind11-3.0.4/docs/advanced/classes.rst
--- pybind11-3.0.1/docs/advanced/classes.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/advanced/classes.rst	2026-04-19 06:01:51.000000000 +0300
@@ -972,9 +972,14 @@
 ===========================
 
 When creating a binding for a class, pybind11 by default makes that binding
-"global" across modules.  What this means is that a type defined in one module
-can be returned from any module resulting in the same Python type.  For
-example, this allows the following:
+"global" across modules. What this means is that instances whose type is
+defined with a ``py::class_`` statement in one module can be passed to or
+returned from a function defined in any other module that is "ABI compatible"
+with the first, i.e., that was built with sufficiently similar versions of
+pybind11 and of the C++ compiler and C++ standard library. The internal data
+structures that pybind11 uses to keep track of its types and instances are
+shared just as they would be if everything were in the same module.
+For example, this allows the following:
 
 .. code-block:: cpp
 
@@ -1376,11 +1381,22 @@
 
 .. code-block:: cpp
 
-   struct OwnsPythonObjects {
-       py::object value = py::none();
+   struct ContainerOwnsPythonObjects {
+       std::vector<py::object> list;
+
+       void append(const py::object &obj) { list.emplace_back(obj); }
+       py::object at(py::ssize_t index) const {
+           if (index >= size() || index < 0) {
+               throw py::index_error("Index out of range");
+           }
+           return list.at(py::size_t(index));
+       }
+       py::ssize_t size() const { return py::ssize_t_cast(list.size()); }
+       void clear() { list.clear(); }
    };
-   py::class_<OwnsPythonObjects> cls(
-       m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
+
+   py::class_<ContainerOwnsPythonObjects> cls(
+       m, "ContainerOwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
            auto *type = &heap_type->ht_type;
            type->tp_flags |= Py_TPFLAGS_HAVE_GC;
            type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
@@ -1389,20 +1405,28 @@
                Py_VISIT(Py_TYPE(self_base));
    #endif
                if (py::detail::is_holder_constructed(self_base)) {
-                   auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
-                   Py_VISIT(self.value.ptr());
+                   auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
+                   for (auto &item : self.list) {
+                       Py_VISIT(item.ptr());
+                   }
                }
                return 0;
            };
            type->tp_clear = [](PyObject *self_base) {
                if (py::detail::is_holder_constructed(self_base)) {
-                   auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
-                   self.value = py::none();
+                   auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
+                   for (auto &item : self.list) {
+                       Py_CLEAR(item.ptr());
+                   }
+                   self.list.clear();
                }
                return 0;
            };
        }));
    cls.def(py::init<>());
-   cls.def_readwrite("value", &OwnsPythonObjects::value);
+   cls.def("append", &ContainerOwnsPythonObjects::append);
+   cls.def("at", &ContainerOwnsPythonObjects::at);
+   cls.def("size", &ContainerOwnsPythonObjects::size);
+   cls.def("clear", &ContainerOwnsPythonObjects::clear);
 
 .. versionadded:: 2.8
diff -Nru pybind11-3.0.1/docs/advanced/embedding.rst pybind11-3.0.4/docs/advanced/embedding.rst
--- pybind11-3.0.1/docs/advanced/embedding.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/advanced/embedding.rst	2026-04-19 06:01:51.000000000 +0300
@@ -18,7 +18,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example)
 
     find_package(pybind11 REQUIRED)  # or `add_subdirectory(pybind11)`
@@ -302,7 +302,7 @@
 
 Once a sub-interpreter is created, you can "activate" it on a thread (and
 acquire its GIL) by creating a :class:`subinterpreter_scoped_activate`
-instance and passing it the sub-intepreter to be activated.  The function
+instance and passing it the sub-interpreter to be activated.  The function
 will acquire the sub-interpreter's GIL and make the sub-interpreter the
 current active interpreter on the current thread for the lifetime of the
 instance. When the :class:`subinterpreter_scoped_activate` instance goes out
@@ -492,4 +492,8 @@
   So you must still consider the thread safety of your C++ code.  Remember, in Python 3.12
   sub-interpreters must be destroyed on the same thread that they were created on.
 
+- When using sub-interpreters in free-threaded python builds, note that creating and destroying
+  sub-interpreters may initiate a "stop-the-world".  Be sure to detach long-running C++ threads
+  from Python thread state (similar to releasing the GIL) to avoid deadlocks.
+
 - Familiarize yourself with :ref:`misc_concurrency`.
diff -Nru pybind11-3.0.1/docs/advanced/functions.rst pybind11-3.0.4/docs/advanced/functions.rst
--- pybind11-3.0.1/docs/advanced/functions.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/advanced/functions.rst	2026-04-19 06:01:51.000000000 +0300
@@ -84,8 +84,8 @@
 | :enum:`return_value_policy::reference_internal`  | If the return value is an lvalue reference or a pointer, the parent object |
 |                                                  | (the implicit ``this``, or ``self`` argument of the called method or       |
 |                                                  | property) is kept alive for at least the lifespan of the return value.     |
-|                                                  | **Otherwise this policy falls back to :enum:`return_value_policy::move`    |
-|                                                  | (see #5528).** Internally, this policy works just like                     |
+|                                                  | **Otherwise this policy falls back to** :enum:`return_value_policy::move`  |
+|                                                  | (see #5528). Internally, this policy works just like                       |
 |                                                  | :enum:`return_value_policy::reference` but additionally applies a          |
 |                                                  | ``keep_alive<0, 1>`` *call policy* (described in the next section) that    |
 |                                                  | prevents the parent object from being garbage collected as long as the     |
diff -Nru pybind11-3.0.1/docs/basics.rst pybind11-3.0.4/docs/basics.rst
--- pybind11-3.0.1/docs/basics.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/basics.rst	2026-04-19 06:01:51.000000000 +0300
@@ -108,6 +108,8 @@
 
     #include <pybind11/pybind11.h>
 
+    namespace py = pybind11;
+
     int add(int i, int j) {
         return i + j;
     }
diff -Nru pybind11-3.0.1/docs/changelog.md pybind11-3.0.4/docs/changelog.md
--- pybind11-3.0.1/docs/changelog.md	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/changelog.md	2026-04-19 06:01:51.000000000 +0300
@@ -13,6 +13,260 @@
 entry" block in pull request descriptions.
 
 
+## Version 3.0.4 (April 18, 2026)
+
+Bug fixes:
+
+- Fixed test builds with installed Eigen 5 by improving `Eigen3` CMake package detection.
+  [#6036](https://github.com/pybind/pybind11/pull/6036)
+
+- Fixed move semantics of `scoped_ostream_redirect` to preserve buffered output and avoid crashes when moved redirects restore stream buffers.
+  [#6033](https://github.com/pybind/pybind11/pull/6033)
+
+- Fixed `py::dynamic_attr()` traversal on Python 3.13+ to correctly propagate `PyObject_VisitManagedDict()` results.
+  [#6032](https://github.com/pybind/pybind11/pull/6032)
+
+- Fixed `std::shared_ptr<T>` fallback casting to avoid unnecessary copy-constructor instantiation in `reference_internal` paths.
+  [#6028](https://github.com/pybind/pybind11/pull/6028)
+
+CI:
+
+- Updated `setup-uv` to the maintained GitHub Action tag scheme.
+  [#6035](https://github.com/pybind/pybind11/pull/6035)
+
+- Updated pre-commit hooks.
+  [#6029](https://github.com/pybind/pybind11/pull/6029)
+
+- Updated GitHub Actions dependencies, including `actions-setup-cmake` and `cibuildwheel`.
+  [#6027](https://github.com/pybind/pybind11/pull/6027)
+
+
+## Version 3.0.3 (March 31, 2026)
+
+Bug fixes:
+
+- Fixed TSS key exhaustion in `implicitly_convertible()` when many implicit conversions are registered across large module sets.
+  [#6020](https://github.com/pybind/pybind11/pull/6020)
+
+- Fixed heap-buffer-overflow in `pythonbuf` with undersized buffers by enforcing a minimum buffer size.
+  [#6019](https://github.com/pybind/pybind11/pull/6019)
+
+- Fixed virtual-inheritance pointer offset crashes when dispatching inherited methods through virtual bases.
+  [#6017](https://github.com/pybind/pybind11/pull/6017)
+
+- Fixed `free(): invalid pointer` crashes during interpreter shutdown with `py::enum_<>` by duplicating late-added `def_property_static` argument strings.
+  [#6015](https://github.com/pybind/pybind11/pull/6015)
+
+- Fixed `function_record` heap-type deallocation to call `PyObject_Free()` and decref the type.
+  [#6010](https://github.com/pybind/pybind11/pull/6010)
+
+- Hardened `PYBIND11_MODULE_PYINIT` and `get_internals()` against module-initialization crashes.
+  [#6018](https://github.com/pybind/pybind11/pull/6018)
+
+- Fixed `static_pointer_cast` build failure with virtual inheritance in `holder_caster_foreign_helpers.h`.
+  [#6014](https://github.com/pybind/pybind11/pull/6014)
+
+- Fixed ambiguous `factory` template specialization that caused compilation failures with nvcc + GCC 14.
+  [#6011](https://github.com/pybind/pybind11/pull/6011)
+
+- Fixed crash in `def_readwrite` for non-smart-holder properties of smart-holder classes.
+  [#6008](https://github.com/pybind/pybind11/pull/6008)
+
+- Fixed memory leak for `py::dynamic_attr()` objects on Python 3.13+ by clearing managed `__dict__` contents during deallocation.
+  [#5999](https://github.com/pybind/pybind11/pull/5999)
+
+- Fixed binding of `noexcept` and ref-qualified (`&`, `&&`) methods inherited from unregistered base classes.
+  [#5992](https://github.com/pybind/pybind11/pull/5992)
+
+Internal:
+
+- Moved `tomlkit` dependency to the dev dependency group.
+  [#5990](https://github.com/pybind/pybind11/pull/5990)
+
+- Switched to newer public CPython APIs (`PyType_GetFlags` and public vectorcall APIs where available).
+  [#6005](https://github.com/pybind/pybind11/pull/6005)
+
+Tests:
+
+- Made an async callback test deterministic by replacing fixed sleep with bounded waiting.
+  [#5986](https://github.com/pybind/pybind11/pull/5986)
+
+CI:
+
+- Re-enabled Android tests in the cibuildwheel workflow.
+  [#6001](https://github.com/pybind/pybind11/pull/6001)
+
+
+## Version 3.0.2 (February 16, 2026)
+
+New Features:
+
+- Added helper functions to `py::array` that return shape and strides as `std::span` when available.
+  [#5974](https://github.com/pybind/pybind11/pull/5974)
+
+Bug fixes:
+
+- Added fallback locking for Python 3.13t where `PyCriticalSection_BeginMutex` is unavailable.
+  [#5981](https://github.com/pybind/pybind11/pull/5981)
+
+- Fixed race condition in `py::make_key_iterator` with free-threaded Python.
+  [#5971](https://github.com/pybind/pybind11/pull/5971)
+
+- MSVC 19.16 and earlier were blocked from using `std::launder` due to internal compiler errors.
+  [#5968](https://github.com/pybind/pybind11/pull/5968)
+
+- Internals destructors were updated to check the owning interpreter before clearing Python objects.
+  [#5965](https://github.com/pybind/pybind11/pull/5965)
+
+- Internals shutdown handling was refined in two iterations before release: an initial finalization-time cleanup was followed by a safety adjustment to avoid late-shutdown `py::cast` segfaults.
+  [#5958](https://github.com/pybind/pybind11/pull/5958)
+  [#5972](https://github.com/pybind/pybind11/pull/5972)
+
+- Fixed ambiguous `str(handle)` construction for `object`-derived types like `kwargs` or `dict` by templatizing the constructor with SFINAE.
+  [#5949](https://github.com/pybind/pybind11/pull/5949)
+
+- Fixed concurrency consistency for `internals_pp_manager` under multiple-interpreters.
+  [#5947](https://github.com/pybind/pybind11/pull/5947)
+
+- Fixed MSVC LNK2001 in C++20 builds when /GL (whole program optimization) is enabled.
+  [#5939](https://github.com/pybind/pybind11/pull/5939)
+
+- Added per-interpreter storage for `gil_safe_call_once_and_store` to make it safe under multi-interpreters.
+  [#5933](https://github.com/pybind/pybind11/pull/5933)
+
+- A workaround for a GCC `-Warray-bounds` false positive in `argument_vector` was added.
+  [#5908](https://github.com/pybind/pybind11/pull/5908)
+
+- Corrected a mistake where support for `__index__` was added, but the type hints did not reflect acceptance of `SupportsIndex` objects. Also fixed a long-standing bug: the complex-caster did not accept `__index__` in `convert` mode.
+  [#5891](https://github.com/pybind/pybind11/pull/5891)
+
+- Fixed `*args/**kwargs` return types. Added type hinting to `py::make_tuple`.
+  [#5881](https://github.com/pybind/pybind11/pull/5881)
+
+- Fixed compiler error in `type_caster_generic` when casting a `T` implicitly convertible from `T*`.
+  [#5873](https://github.com/pybind/pybind11/pull/5873)
+
+- Updated `py::native_enum` bindings to unregister enum types on destruction, preventing a use-after-free when returning a destroyed enum instance.
+  [#5871](https://github.com/pybind/pybind11/pull/5871)
+
+- Fixed undefined behavior that occurred when importing pybind11 modules from non-main threads created by C API modules or embedded python interpreters.
+  [#5870](https://github.com/pybind/pybind11/pull/5870)
+
+- Fixed dangling pointer in `internals::registered_types_cpp_fast`.
+  [#5867](https://github.com/pybind/pybind11/pull/5867)
+
+- Added support for `std::shared_ptr<T>` when loading module-local or conduit types from other modules.
+  [#5862](https://github.com/pybind/pybind11/pull/5862)
+
+- Fixed thread-safety issues if types were concurrently registered while `get_local_type_info()` was called in free threaded Python.
+  [#5856](https://github.com/pybind/pybind11/pull/5856)
+
+- Fixed py::float_ casting and py::int_ and py::float_ type hints.
+  [#5839](https://github.com/pybind/pybind11/pull/5839)
+
+- Fixed two `smart_holder` bugs in `shared_ptr` and `unique_ptr` adoption with multiple/virtual inheritance:
+    - `shared_ptr` to-Python caster was updated to register the correct subobject pointer (fixes #5786).
+    - `unique_ptr` adoption was updated to own the proper object start while aliasing subobject pointers for registration, which fixed MSVC crashes during destruction.
+  [#5836](https://github.com/pybind/pybind11/pull/5836)
+
+- Constrained `accessor::operator=` templates to avoid obscuring special members.
+  [#5832](https://github.com/pybind/pybind11/pull/5832)
+
+- Fixed crash that can occur when finalizers acquire and release the GIL.
+  [#5828](https://github.com/pybind/pybind11/pull/5828)
+
+- Fixed compiler detection in `pybind11/detail/pybind11_namespace_macros.h` for clang-cl on Windows, to address warning suppression macros.
+  [#5816](https://github.com/pybind/pybind11/pull/5816)
+
+- Fixed compatibility with CMake policy CMP0190 by not always requiring a Python interpreter when cross-compiling.
+  [#5829](https://github.com/pybind/pybind11/pull/5829)
+
+- Added a static assertion to disallow `keep_alive` and `call_guard` on properties.
+  [#5533](https://github.com/pybind/pybind11/pull/5533)
+
+Internal:
+
+- CMake policy limit was set to 4.1.
+  [#5944](https://github.com/pybind/pybind11/pull/5944)
+
+- Improved performance of function calls between Python and C++ by switching to the "vectorcall" calling protocol.
+  [#5948](https://github.com/pybind/pybind11/pull/5948)
+
+- Many C-style casts were replaced with C++-style casts.
+  [#5930](https://github.com/pybind/pybind11/pull/5930)
+
+- Added `cast_sources` abstraction to `type_caster_generic`.
+  [#5866](https://github.com/pybind/pybind11/pull/5866)
+
+- Improved the performance of from-Python conversions of legacy pybind11 enum objects bound by `py::enum_`.
+  [#5860](https://github.com/pybind/pybind11/pull/5860)
+
+- Reduced size overhead by deduplicating functions' readable signatures and type information.
+  [#5857](https://github.com/pybind/pybind11/pull/5857)
+
+- Used new Python 3.14 C APIs when available.
+  [#5854](https://github.com/pybind/pybind11/pull/5854)
+
+- Improved performance of function dispatch and type casting by porting two-level type info lookup strategy from nanobind.
+  [#5842](https://github.com/pybind/pybind11/pull/5842)
+
+- Updated `.gitignore` to exclude `__pycache__/` directories.
+  [#5838](https://github.com/pybind/pybind11/pull/5838)
+
+- Changed internals to use `thread_local` instead of `thread_specific_storage` for increased performance.
+  [#5834](https://github.com/pybind/pybind11/pull/5834)
+
+- Reduced function call overhead by using thread_local for loader_life_support when possible.
+  [#5830](https://github.com/pybind/pybind11/pull/5830)
+
+- Removed heap allocation for the C++ argument array when dispatching functions with 6 or fewer arguments.
+  [#5824](https://github.com/pybind/pybind11/pull/5824)
+
+
+Documentation:
+
+- Fixed docstring for `long double` complex types to use `numpy.clongdouble` instead of the deprecated `numpy.longcomplex` (removed in NumPy 2.0).
+  [#5952](https://github.com/pybind/pybind11/pull/5952)
+
+- The "Supported compilers" and "Supported platforms" sections in the main `README.rst` were replaced with a new "Supported platforms & compilers" section that points to the CI test matrix as the living source of truth.
+  [#5910](https://github.com/pybind/pybind11/pull/5910)
+
+- Fixed documentation formatting.
+  [#5903](https://github.com/pybind/pybind11/pull/5903)
+
+- Updated upgrade notes for `py::native_enum`.
+  [#5885](https://github.com/pybind/pybind11/pull/5885)
+
+- Clarified in the docs to what extent bindings are global.
+  [#5859](https://github.com/pybind/pybind11/pull/5859)
+
+
+Tests:
+
+- Fixed deadlock in a free-threading test by releasing the GIL while waiting on synchronization.
+  [#5973](https://github.com/pybind/pybind11/pull/5973)
+
+- Calls to `env.deprecated_call()` were replaced with direct calls to `pytest.deprecated_call()`.
+  [#5893](https://github.com/pybind/pybind11/pull/5893)
+
+- Updated pytest configuration to use `log_level` instead of `log_cli_level`.
+  [#5890](https://github.com/pybind/pybind11/pull/5890)
+
+
+CI:
+
+- Added CI tests for windows-11-arm with clang/MSVC (currently python 3.13), windows-11-arm with clang/mingw (currently python 3.12).
+  [#5932](https://github.com/pybind/pybind11/pull/5932)
+
+- These clang-tidy rules were added: `readability-redundant-casting`, `readability-redundant-inline-specifier`, `readability-redundant-member-init`
+  [#5924](https://github.com/pybind/pybind11/pull/5924)
+
+- Replaced deprecated macos-13 runners with macos-15-intel in CI.
+  [#5916](https://github.com/pybind/pybind11/pull/5916)
+
+- Restored `runs-on: windows-latest` in CI.
+  [#5835](https://github.com/pybind/pybind11/pull/5835)
+
 ## Version 3.0.1 (August 22, 2025)
 
 Bug fixes:
@@ -167,7 +421,7 @@
   [#5665](https://github.com/pybind/pybind11/pull/5665) and consolidate code
   [#5670](https://github.com/pybind/pybind11/pull/5670).
 
-- Added API in `pybind11/subinterpreter.h` for embedding sub-intepreters (requires Python 3.12+).
+- Added API in `pybind11/subinterpreter.h` for embedding sub-interpreters (requires Python 3.12+).
   [#5666](https://github.com/pybind/pybind11/pull/5666)
 
 - `py::native_enum` was added, for conversions between Python's native
@@ -1213,7 +1467,7 @@
 - Optimize Eigen sparse matrix casting by removing unnecessary
   temporary. [#4064](https://github.com/pybind/pybind11/pull/4064)
 - Avoid potential implicit copy/assignment constructors causing double
-  free in `strdup_gaurd`.
+  free in `strdup_guard`.
   [#3905](https://github.com/pybind/pybind11/pull/3905)
 - Enable clang-tidy checks `misc-definitions-in-headers`,
   `modernize-loop-convert`, and `modernize-use-nullptr`.
diff -Nru pybind11-3.0.1/docs/compiling.rst pybind11-3.0.4/docs/compiling.rst
--- pybind11-3.0.1/docs/compiling.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/compiling.rst	2026-04-19 06:01:51.000000000 +0300
@@ -18,7 +18,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example LANGUAGES CXX)
 
     set(PYBIND11_FINDPYTHON ON)
@@ -447,7 +447,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example LANGUAGES CXX)
 
     find_package(pybind11 REQUIRED)
@@ -492,7 +492,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example LANGUAGES CXX)
 
     find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED)
@@ -570,7 +570,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example LANGUAGES CXX)
 
     find_package(pybind11 REQUIRED)  # or add_subdirectory(pybind11)
@@ -628,7 +628,7 @@
 
 .. code-block:: cmake
 
-    cmake_minimum_required(VERSION 3.15...4.0)
+    cmake_minimum_required(VERSION 3.15...4.2)
     project(example LANGUAGES CXX)
 
     find_package(pybind11 REQUIRED)  # or add_subdirectory(pybind11)
diff -Nru pybind11-3.0.1/docs/requirements.txt pybind11-3.0.4/docs/requirements.txt
--- pybind11-3.0.1/docs/requirements.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/requirements.txt	2026-04-19 06:01:51.000000000 +0300
@@ -23,8 +23,6 @@
     # via requests
 imagesize==1.4.1
     # via sphinx
-importlib-metadata==8.7.0
-    # via sphinx
 jinja2==3.1.6
     # via
     #   myst-parser
@@ -43,13 +41,13 @@
     # via -r requirements.in
 packaging==24.0
     # via sphinx
-pygments==2.17.2
+pygments==2.20.0
     # via
     #   furo
     #   sphinx
 pyyaml==6.0.2
     # via myst-parser
-requests==2.32.4
+requests==2.33.0
     # via sphinx
 snowballstemmer==2.2.0
     # via sphinx
@@ -85,7 +83,5 @@
     # via sphinx
 sphinxcontrib-svg2pdfconverter==1.2.2
     # via -r requirements.in
-urllib3==2.5.0
+urllib3==2.6.3
     # via requests
-zipp==3.23.0
-    # via importlib-metadata
diff -Nru pybind11-3.0.1/docs/upgrade.rst pybind11-3.0.4/docs/upgrade.rst
--- pybind11-3.0.1/docs/upgrade.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/docs/upgrade.rst	2026-04-19 06:01:51.000000000 +0300
@@ -48,6 +48,8 @@
 ``enum.Enum`` or related subclasses. This provides improved integration with
 Python's enum system, compared to the older (now deprecated) ``py::enum_``.
 See `#5555 <https://github.com/pybind/pybind11/pull/5555>`_ for details.
+Note that ``#include <pybind11/native_enum.h>`` is not included automatically
+and must be added explicitly.
 
 Functions exposed with pybind11 are now pickleable. This removes a
 long-standing obstacle when using pybind11-bound functions with Python features
diff -Nru pybind11-3.0.1/.github/CONTRIBUTING.md pybind11-3.0.4/.github/CONTRIBUTING.md
--- pybind11-3.0.1/.github/CONTRIBUTING.md	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/CONTRIBUTING.md	2026-04-19 06:01:51.000000000 +0300
@@ -165,7 +165,7 @@
 * Use `cmake --build build -j12` to build with 12 cores (for example).
 * Use `-G` and the name of a generator to use something different. `cmake
   --help` lists the generators available.
-      - On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give
+      - On Unix, setting `CMAKE_GENERATOR=Ninja` in your environment will give
         you automatic multithreading on all your CMake projects!
 * Open the `CMakeLists.txt` with QtCreator to generate for that IDE.
 * You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file
diff -Nru pybind11-3.0.1/.github/dependabot.yml pybind11-3.0.4/.github/dependabot.yml
--- pybind11-3.0.1/.github/dependabot.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/dependabot.yml	2026-04-19 06:01:51.000000000 +0300
@@ -4,12 +4,8 @@
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
-      interval: "weekly"
+      interval: "monthly"
     groups:
       actions:
         patterns:
           - "*"
-    ignore:
-      - dependency-name: actions/checkout
-        versions:
-          - "<5"
diff -Nru pybind11-3.0.1/.github/workflows/ci.yml pybind11-3.0.4/.github/workflows/ci.yml
--- pybind11-3.0.1/.github/workflows/ci.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/ci.yml	2026-04-19 06:01:51.000000000 +0300
@@ -84,11 +84,11 @@
             python-version: '3.12'
             cmake-args: -DPYBIND11_TEST_SMART_HOLDER=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
           - runs-on: ubuntu-latest
-            python-version: '3.13t'
+            python-version: '3.14t'
             cmake-args: -DCMAKE_CXX_STANDARD=20 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
           - runs-on: ubuntu-latest
             python-version: '3.14'
-            cmake-args: -DCMAKE_CXX_STANDARD=14 -DCMAKE_CXX_FLAGS="-DPYBIND11_HAS_SUBINTERPRETER_SUPPORT=0"
+            cmake-args: -DCMAKE_CXX_STANDARD=14
           - runs-on: ubuntu-latest
             python-version: 'pypy-3.10'
             cmake-args: -DCMAKE_CXX_STANDARD=14
@@ -96,22 +96,22 @@
             python-version: 'graalpy-24.1'
 
           # No SciPy for macOS ARM
-          - runs-on: macos-13
+          - runs-on: macos-15-intel
             python-version: '3.8'
             cmake-args: -DCMAKE_CXX_STANDARD=14
-          - runs-on: macos-13
+          - runs-on: macos-15-intel
             python-version: '3.11'
             cmake-args: -DPYBIND11_TEST_SMART_HOLDER=ON
+          - runs-on: macos-15-intel
+            python-version: '3.13'
+            cmake-args: -DCMAKE_CXX_STANDARD=11
           - runs-on: macos-latest
             python-version: '3.12'
             cmake-args: -DCMAKE_CXX_STANDARD=17 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
-          - runs-on: macos-13
-            python-version: '3.13t'
-            cmake-args: -DCMAKE_CXX_STANDARD=11
           - runs-on: macos-latest
             python-version: '3.14t'
             cmake-args: -DCMAKE_CXX_STANDARD=20
-          - runs-on: macos-13
+          - runs-on: macos-15-intel
             python-version: 'pypy-3.10'
             cmake-args: -DCMAKE_CXX_STANDARD=17
           - runs-on: macos-latest
@@ -139,9 +139,6 @@
             python-version: '3.13'
             cmake-args: -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL
           - runs-on: windows-latest
-            python-version: '3.13t'
-            cmake-args: -DCMAKE_CXX_STANDARD=17
-          - runs-on: windows-latest
             python-version: '3.14'
             cmake-args: -DCMAKE_CXX_STANDARD=20
           - runs-on: windows-latest
@@ -179,18 +176,19 @@
 
     name: "🐍 ${{ matrix.python-version }} • ${{ matrix.runs-on }} • x64 inplace C++14"
     runs-on: ${{ matrix.runs-on }}
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: ${{ matrix.python-version }}
         allow-prereleases: true
 
     - name: Install uv
-      uses: astral-sh/setup-uv@v6
+      uses: astral-sh/setup-uv@v8.0.0
       with:
         enable-cache: true
 
@@ -202,7 +200,8 @@
       if: runner.os != 'Windows'
       run: echo "CMAKE_GENERATOR=Ninja" >> "$GITHUB_ENV"
 
-    # More-or-less randomly adding a few extra flags here
+    # More-or-less randomly adding a few extra flags here.
+    # In particular, use this one to test the next ABI bump (internals version).
     - name: Configure
       run: >
         cmake -S. -B.
@@ -212,6 +211,7 @@
         -DDOWNLOAD_CATCH=ON
         -DDOWNLOAD_EIGEN=ON
         -DCMAKE_CXX_STANDARD=14
+        -DPYBIND11_INTERNALS_VERSION=10000000
 
     # Checks to makes sure defining `_` is allowed
     # Triggers EHsc missing error on Windows
@@ -226,6 +226,7 @@
       run: cmake --build . --target pytest
 
     - name: Compiled tests
+      timeout-minutes: 3
       run: cmake --build . --target cpptest
 
     - name: Interface test
@@ -236,13 +237,25 @@
 
 
   manylinux:
-    name: Manylinux on 🐍 3.13t • GIL
+    name: Manylinux on 🐍 ${{ matrix.python-version }} (${{ matrix.container }})
     if: github.event.pull_request.draft == false
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - container: quay.io/pypa/manylinux_2_28_x86_64:latest
+            python-version: '3.13t'
+          - container: quay.io/pypa/musllinux_1_2_x86_64:latest
+            python-version: '3.13t'
+          - container: quay.io/pypa/manylinux_2_28_x86_64:latest
+            python-version: '3.14t'
+          - container: quay.io/pypa/musllinux_1_2_x86_64:latest
+            python-version: '3.14t'
     runs-on: ubuntu-latest
     timeout-minutes: 40
-    container: quay.io/pypa/musllinux_1_2_x86_64:latest
+    container: ${{ matrix.container }}
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           fetch-depth: 0
 
@@ -253,7 +266,7 @@
         run: uv tool install ninja
 
       - name: Configure via preset
-        run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV=python3.13t
+        run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV="${{ matrix.python-version }}"
 
       - name: Build C++11
         run: cmake --build --preset venv
@@ -277,9 +290,10 @@
 
     name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64"
     runs-on: ubuntu-latest
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python ${{ matrix.python-version }} (deadsnakes)
       uses: deadsnakes/action@v3.2.0
@@ -288,11 +302,11 @@
         debug: ${{ matrix.python-debug }}
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Valgrind cache
       if: matrix.valgrind
-      uses: actions/cache@v4
+      uses: actions/cache@v5
       id: cache-valgrind
       with:
         path: valgrind
@@ -330,6 +344,7 @@
       run: cmake --build --preset default --target pytest
 
     - name: C++ tests
+      timeout-minutes: 3
       run: cmake --build --preset default --target cpptest
 
     - name: Visibility test
@@ -364,9 +379,10 @@
 
     name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64${{ matrix.cxx_flags && ' • cxx_flags' || '' }}"
     container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}"
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Add wget and python3
       run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev
@@ -388,6 +404,7 @@
       run: cmake --build build --target pytest
 
     - name: C++ tests
+      timeout-minutes: 3
       run: cmake --build build --target cpptest
 
     - name: Interface test
@@ -401,9 +418,10 @@
     runs-on: ubuntu-latest
     name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04"
     container: nvidia/cuda:12.2.0-devel-ubuntu22.04
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND
     - name: Install 🐍 3
@@ -427,7 +445,7 @@
 #    container: centos:8
 #
 #    steps:
-#    - uses: actions/checkout@v4
+#    - uses: actions/checkout@v6
 #
 #    - name: Add Python 3 and a few requirements
 #      run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules
@@ -464,16 +482,22 @@
 
 
   # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds
-  ubuntu-nvhpc7:
+  ubuntu-nvhpc:
     if: github.event.pull_request.draft == false
-    runs-on: ubuntu-22.04
-    name: "🐍 3 • NVHPC 23.5 • C++17 • x64"
+    runs-on: ubuntu-24.04
+    name: "🐍 3 • NVHPC 25.11 • C++17 • x64"
+    timeout-minutes: 90
 
     env:
       # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND
       DEBIAN_FRONTEND: 'noninteractive'
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
+
+    - name: Clean out unused stuff to save space
+      run: |
+        sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL
+        sudo apt-get clean
 
     - name: Add NVHPC Repo
       run: |
@@ -482,10 +506,11 @@
 
     - name: Install 🐍 3 & NVHPC
       run: |
-        sudo apt-get update -y && \
-        sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \
-        sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \
+        sudo apt-get update -y
+        sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy
+        sudo apt-get install -y --no-install-recommends nvhpc-25-11
         sudo rm -rf /var/lib/apt/lists/*
+        apt-cache depends nvhpc-25-11
         python3 -m pip install --upgrade pip
         python3 -m pip install --upgrade pytest
 
@@ -495,7 +520,7 @@
       shell: bash
       run: |
         source /etc/profile.d/modules.sh
-        module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5
+        module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/25.11
         cmake -S . -B build -DDOWNLOAD_CATCH=ON \
                             -DCMAKE_CXX_STANDARD=17 \
                             -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
@@ -503,12 +528,13 @@
                             -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp"
 
     - name: Build
-      run: cmake --build build -j 2 --verbose
+      run: cmake --build build -j $(nproc) --verbose
 
     - name: Python tests
       run: cmake --build build --target pytest
 
     - name: C++ tests
+      timeout-minutes: 3
       run: cmake --build build --target cpptest
 
     - name: Interface test
@@ -532,9 +558,10 @@
 
     name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }} • x64${{ matrix.cxx_flags && ' • cxx_flags' || '' }}"
     container: "gcc:${{ matrix.gcc }}"
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Add Python 3
       run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev
@@ -543,7 +570,7 @@
       run: python3 -m pip install --upgrade pip
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Configure
       shell: bash
@@ -562,6 +589,7 @@
       run: cmake --build build --target pytest
 
     - name: C++ tests
+      timeout-minutes: 3
       run: cmake --build build --target cpptest
 
     - name: Interface test
@@ -593,11 +621,12 @@
   icc:
     if: github.event.pull_request.draft == false
     runs-on: ubuntu-22.04
+    timeout-minutes: 90
 
     name: "🐍 3 • ICC latest • x64"
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Add apt repo
       run: |
@@ -643,6 +672,7 @@
         cmake --build build-11 --target check
 
     - name: C++ tests C++11
+      timeout-minutes: 3
       run: |
         set +e; source /opt/intel/oneapi/setvars.sh; set -e
         cmake --build build-11 --target cpptest
@@ -680,6 +710,7 @@
         cmake --build build-17 --target check
 
     - name: C++ tests C++17
+      timeout-minutes: 3
       run: |
         set +e; source /opt/intel/oneapi/setvars.sh; set -e
         cmake --build build-17 --target cpptest
@@ -707,10 +738,11 @@
 
     name: "🐍 3 • ${{ matrix.container }} • x64"
     container: "${{ matrix.container }}"
+    timeout-minutes: 90
 
     steps:
     - name: Latest actions/checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v6
 
     - name: Add Python 3.8
       if: matrix.container == 'almalinux:8'
@@ -750,6 +782,7 @@
       run: cmake --build build --target pytest
 
     - name: C++ tests
+      timeout-minutes: 3
       run: cmake --build build --target cpptest
 
     - name: Interface test
@@ -765,9 +798,11 @@
     name: "🐍 3.9 • Debian • x86 •  Install"
     runs-on: ubuntu-latest
     container: i386/debian:bullseye
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v1  # v1 is required to run inside docker
+    # v1 required for i386/debian container; pinned to SHA to prevent dependabot updates
+    - uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9  # v1
 
     - name: Install requirements
       run: |
@@ -809,11 +844,12 @@
     if: github.event.pull_request.draft == false
     name: "Documentation build test"
     runs-on: ubuntu-latest
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
-    - uses: actions/setup-python@v5
+    - uses: actions/setup-python@v6
       with:
         python-version: "3.x"
 
@@ -855,12 +891,13 @@
 
     name: "🐍 ${{ matrix.python }} • MSVC 2022 • x86 ${{ matrix.args }}"
     runs-on: windows-2022
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python ${{ matrix.python }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: ${{ matrix.python }}
         architecture: x86
@@ -869,7 +906,7 @@
           ${{ matrix.python == '3.13' && runner.os == 'Windows' }}
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Prepare MSVC
       uses: ilammy/msvc-dev-cmd@v1.13.0
@@ -907,18 +944,19 @@
 
     name: "🐍 ${{ matrix.python }} • MSVC 2022 (Debug) • x86 ${{ matrix.args }}"
     runs-on: windows-2022
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python ${{ matrix.python }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: ${{ matrix.python }}
         architecture: x86
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Prepare MSVC
       uses: ilammy/msvc-dev-cmd@v1.13.0
@@ -955,12 +993,13 @@
 
     name: "🐍 ${{ matrix.python }} • MSVC 2022 C++20 • x64"
     runs-on: windows-2022
+    timeout-minutes: 90
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python ${{ matrix.python }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: ${{ matrix.python }}
 
@@ -968,7 +1007,7 @@
       run: python3 -m pip install -r tests/requirements.txt
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Configure C++20
       run: >
@@ -985,6 +1024,7 @@
       run: cmake --build build --target pytest
 
     - name: C++20 tests
+      timeout-minutes: 3
       run: cmake --build build --target cpptest -j 2
 
     - name: Interface test C++20
@@ -1012,6 +1052,7 @@
     if: github.event.pull_request.draft == false
     name: "🐍 3 • windows-latest • ${{ matrix.sys }}"
     runs-on: windows-latest
+    timeout-minutes: 90
     defaults:
       run:
         shell: msys2 {0}
@@ -1019,8 +1060,15 @@
       fail-fast: false
       matrix:
         include:
-          - { sys: mingw64, env: x86_64 }
-          - { sys: mingw32, env: i686 }
+          - sys: mingw32
+            env: i686
+            extra_install: ""
+          - sys: mingw64
+            env: x86_64
+            extra_install: |
+              mingw-w64-x86_64-python-numpy
+              mingw-w64-x86_64-python-scipy
+              mingw-w64-x86_64-eigen3
     steps:
     - uses: msys2/setup-msys2@v2
       with:
@@ -1034,17 +1082,9 @@
           mingw-w64-${{matrix.env}}-python-pytest
           mingw-w64-${{matrix.env}}-boost
           mingw-w64-${{matrix.env}}-catch
+          ${{ matrix.extra_install }}
 
-    - uses: msys2/setup-msys2@v2
-      if: matrix.sys == 'mingw64'
-      with:
-        msystem: ${{matrix.sys}}
-        install: >-
-          mingw-w64-${{matrix.env}}-python-numpy
-          mingw-w64-${{matrix.env}}-python-scipy
-          mingw-w64-${{matrix.env}}-eigen3
-
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Configure C++11
       # LTO leads to many undefined reference like
@@ -1061,6 +1101,7 @@
       run: cmake --build build --target pytest -j 2
 
     - name: C++11 tests
+      timeout-minutes: 3
       run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2
 
     - name: Interface test C++11
@@ -1085,6 +1126,7 @@
       run: cmake --build build2 --target pytest -j 2
 
     - name: C++14 tests
+      timeout-minutes: 3
       run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2
 
     - name: Interface test C++14
@@ -1109,6 +1151,7 @@
       run: cmake --build build3 --target pytest -j 2
 
     - name: C++17 tests
+      timeout-minutes: 3
       run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target cpptest -j 2
 
     - name: Interface test C++17
@@ -1126,6 +1169,7 @@
         python: ['3.10']
 
     runs-on: "${{ matrix.os }}"
+    timeout-minutes: 90
 
     name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest"
 
@@ -1134,18 +1178,18 @@
         run: env
 
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v6
 
       - name: Set up Clang
-        uses: egor-tensin/setup-clang@v1
+        uses: egor-tensin/setup-clang@v2
 
       - name: Setup Python ${{ matrix.python }}
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: ${{ matrix.python }}
 
       - name: Update CMake
-        uses: jwlawson/actions-setup-cmake@v2.0
+        uses: jwlawson/actions-setup-cmake@v2.2
 
       - name: Install ninja-build tool
         uses: seanmiddleditch/gha-setup-ninja@v6
@@ -1179,6 +1223,7 @@
         run: cmake --build . --target pytest -j 2
 
       - name: C++ tests
+        timeout-minutes: 3
         run: cmake --build . --target cpptest -j 2
 
       - name: Interface test
@@ -1190,57 +1235,49 @@
       - name: Clean directory
         run: git clean -fdx
 
-  macos_brew_install_llvm:
+  # Clang with MSVC/Windows SDK toolchain + python.org CPython (Windows ARM)
+  windows_arm_clang_msvc:
     if: github.event.pull_request.draft == false
-    name: "macos-13 • brew install llvm"
-    runs-on: macos-13
 
-    env:
-      # https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew
-      LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib'
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-11-arm]
+        python: ['3.13']
 
-    steps:
-      - name: Update PATH
-        run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
+    runs-on: "${{ matrix.os }}"
+    timeout-minutes: 90
+
+    name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-msvc"
 
+    steps:
       - name: Show env
         run: env
 
       - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Show Clang++ version before brew install llvm
-        run: clang++ --version
+        uses: actions/checkout@v6
 
-      - name: brew install llvm
-        run: brew install llvm
-
-      - name: Show Clang++ version after brew install llvm
-        run: clang++ --version
-
-      - name: Update CMake
-        uses: jwlawson/actions-setup-cmake@v2.0
+      - name: Setup Python ${{ matrix.python }}
+        uses: actions/setup-python@v6
+        with:
+          python-version: ${{ matrix.python }}
+          architecture: arm64
 
       - name: Run pip installs
         run: |
-          python3 -m pip install --upgrade pip
-          python3 -m pip install -r tests/requirements.txt
-          python3 -m pip install numpy
-          python3 -m pip install scipy
-
-      - name: Show CMake version
-        run: cmake --version
+          python -m pip install --upgrade pip
+          python -m pip install -r tests/requirements.txt
 
-      - name: CMake Configure
+      - name: Configure CMake
         run: >
-          cmake -S . -B .
-          -DPYBIND11_WERROR=ON
+          cmake -G Ninja -S . -B .
+          -DPYBIND11_WERROR=OFF
           -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
           -DDOWNLOAD_CATCH=ON
           -DDOWNLOAD_EIGEN=ON
           -DCMAKE_CXX_COMPILER=clang++
-          -DCMAKE_CXX_STANDARD=17
-          -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
+          -DCMAKE_CXX_STANDARD=20
+          -DPython_ROOT_DIR="$env:Python_ROOT_DIR"
 
       - name: Build
         run: cmake --build . -j 2
@@ -1249,6 +1286,7 @@
         run: cmake --build . --target pytest -j 2
 
       - name: C++ tests
+        timeout-minutes: 3
         run: cmake --build . --target cpptest -j 2
 
       - name: Interface test
@@ -1257,23 +1295,75 @@
       - name: Visibility test
         run: cmake --build . --target test_cross_module_rtti -j 2
 
-      - name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE
-        run: >
-          cmake -S . -B build_partial
-          -DPYBIND11_WERROR=ON
-          -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-          -DDOWNLOAD_CATCH=ON
-          -DDOWNLOAD_EIGEN=ON
-          -DCMAKE_CXX_COMPILER=clang++
-          -DCMAKE_CXX_STANDARD=17
-          -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
-          "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
+  # Clang in MSYS2/MinGW-w64 CLANGARM64 toolchain + MSYS2 Python (Windows ARM)
+  windows_arm_clang_msys2:
+    if: github.event.pull_request.draft == false
+
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-11-arm]
 
-      - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE
-        run: cmake --build build_partial -j 2
+    runs-on: "${{ matrix.os }}"
+    timeout-minutes: 90
 
-      - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
-        run: cmake --build build_partial --target pytest -j 2
+    name: "${{ matrix.os }} • clang-msys2"
 
-      - name: Clean directory
-        run: git clean -fdx
+    defaults:
+      run:
+        shell: msys2 {0}
+
+    steps:
+    - uses: actions/checkout@v6
+      with:
+        submodules: true
+
+    - uses: msys2/setup-msys2@v2
+      with:
+        msystem: CLANGARM64
+        update: true
+        install: |
+          mingw-w64-clang-aarch64-cmake
+          mingw-w64-clang-aarch64-clang
+          mingw-w64-clang-aarch64-ninja
+          mingw-w64-clang-aarch64-python-pip
+          mingw-w64-clang-aarch64-python-pytest
+          mingw-w64-clang-aarch64-python-numpy
+
+    - name: Debug info
+      run: |
+        clang++ --version
+        cmake --version
+        ninja --version
+        python --version
+
+    - name: Run pip installs
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install -r tests/requirements.txt
+
+    - name: Configure CMake
+      run: >-
+        cmake -S . -B build
+        -DPYBIND11_WERROR=OFF
+        -DDOWNLOAD_CATCH=ON
+        -DDOWNLOAD_EIGEN=ON
+        -DCMAKE_CXX_COMPILER=clang++
+        -DCMAKE_CXX_STANDARD=20
+        -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
+
+    - name: Build
+      run: cmake --build build -j 2
+
+    - name: Python tests
+      run: cmake --build build --target pytest -j 2
+
+    - name: C++ tests
+      timeout-minutes: 3
+      run: PYTHONHOME=/clangarm64 PYTHONPATH=/clangarm64 cmake --build build --target cpptest -j 2
+
+    - name: Interface test
+      run: cmake --build build --target test_cmake_build -j 2
+
+    - name: Visibility test
+      run: cmake --build build --target test_cross_module_rtti -j 2
diff -Nru pybind11-3.0.1/.github/workflows/configure.yml pybind11-3.0.4/.github/workflows/configure.yml
--- pybind11-3.0.1/.github/workflows/configure.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/configure.yml	2026-04-19 06:01:51.000000000 +0300
@@ -35,28 +35,28 @@
         - runs-on: ubuntu-24.04
           cmake: "3.29"
 
-        - runs-on: macos-13
+        - runs-on: macos-15-intel
           cmake: "3.15"
 
         - runs-on: macos-14
-          cmake: "4.0"
+          cmake: "4.2"
 
         - runs-on: windows-latest
-          cmake: "4.0"
+          cmake: "4.2"
 
     name: 🐍 3.11 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
     runs-on: ${{ matrix.runs-on }}
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python 3.11
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: 3.11
 
     - name: Install uv
-      uses: astral-sh/setup-uv@v6
+      uses: astral-sh/setup-uv@v8.0.0
 
     - name: Prepare env
       run: uv pip install --python=python --system -r tests/requirements.txt
@@ -64,7 +64,7 @@
     # An action for adding a specific version of CMake:
     #   https://github.com/jwlawson/actions-setup-cmake
     - name: Setup CMake ${{ matrix.cmake }}
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
       with:
         cmake-version: ${{ matrix.cmake }}
 
diff -Nru pybind11-3.0.1/.github/workflows/docs-link.yml pybind11-3.0.4/.github/workflows/docs-link.yml
--- pybind11-3.0.1/.github/workflows/docs-link.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/docs-link.yml	2026-04-19 06:01:51.000000000 +0300
@@ -19,7 +19,7 @@
     runs-on: ubuntu-latest
     if: github.event.repository.fork == false
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
 
       - name: Check for docs changes
         id: docs_changes
diff -Nru pybind11-3.0.1/.github/workflows/format.yml pybind11-3.0.4/.github/workflows/format.yml
--- pybind11-3.0.1/.github/workflows/format.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/format.yml	2026-04-19 06:01:51.000000000 +0300
@@ -25,8 +25,8 @@
     name: Format
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
+    - uses: actions/checkout@v6
+    - uses: actions/setup-python@v6
       with:
         python-version: "3.x"
     - name: Add matchers
@@ -40,7 +40,7 @@
     runs-on: ubuntu-latest
     container: silkeh/clang:20
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Install requirements
       run: apt-get update && apt-get install -y git python3-dev python3-pytest ninja-build
diff -Nru pybind11-3.0.1/.github/workflows/labeler.yml pybind11-3.0.4/.github/workflows/labeler.yml
--- pybind11-3.0.1/.github/workflows/labeler.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/labeler.yml	2026-04-19 06:01:51.000000000 +0300
@@ -14,7 +14,7 @@
       pull-requests: write
     steps:
 
-    - uses: actions/labeler@v5
+    - uses: actions/labeler@v6
       if: >
         github.event.pull_request.merged == true &&
         !startsWith(github.event.pull_request.title, 'chore(deps):') &&
diff -Nru pybind11-3.0.1/.github/workflows/nightlies.yml pybind11-3.0.4/.github/workflows/nightlies.yml
--- pybind11-3.0.1/.github/workflows/nightlies.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/nightlies.yml	2026-04-19 06:01:51.000000000 +0300
@@ -20,12 +20,12 @@
     if: github.repository_owner == 'pybind'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           fetch-depth: 0
 
       - name: Install uv
-        uses: astral-sh/setup-uv@v6
+        uses: astral-sh/setup-uv@v8.0.0
 
       - name: Build SDist and wheels
         run: |
@@ -33,7 +33,7 @@
           nox -s build
           nox -s build_global
 
-      - uses: actions/upload-artifact@v4
+      - uses: actions/upload-artifact@v7
         with:
           name: Packages
           path: dist/*
@@ -44,7 +44,7 @@
     needs: [build_wheel]
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v8
         with:
           name: Packages
           path: dist
@@ -53,7 +53,7 @@
         run: ls -lha dist/*.whl
 
       - name: Upload wheel to Anaconda Cloud as nightly
-        uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2
+        uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
         with:
           artifacts_path: dist
           anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
diff -Nru pybind11-3.0.1/.github/workflows/pip.yml pybind11-3.0.4/.github/workflows/pip.yml
--- pybind11-3.0.1/.github/workflows/pip.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/pip.yml	2026-04-19 06:01:51.000000000 +0300
@@ -23,15 +23,15 @@
     runs-on: windows-latest
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup 🐍 3.8
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: 3.8
 
     - name: Install uv
-      uses: astral-sh/setup-uv@v6
+      uses: astral-sh/setup-uv@v8.0.0
 
     - name: Prepare env
       run: uv pip install --system -r tests/requirements.txt
@@ -47,15 +47,15 @@
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup 🐍 3.8
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: 3.8
 
     - name: Install uv
-      uses: astral-sh/setup-uv@v6
+      uses: astral-sh/setup-uv@v8.0.0
 
     - name: Prepare env
       run: uv pip install --system -r tests/requirements.txt twine nox
@@ -72,13 +72,13 @@
       run: twine check dist/*
 
     - name: Save standard package
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v7
       with:
         name: standard
         path: dist/pybind11-*
 
     - name: Save global package
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v7
       with:
         name: global
         path: dist/*global-*
@@ -100,10 +100,10 @@
 
     steps:
     # Downloads all to directories matching the artifact names
-    - uses: actions/download-artifact@v5
+    - uses: actions/download-artifact@v8
 
     - name: Generate artifact attestation for sdist and wheel
-      uses: actions/attest-build-provenance@v2
+      uses: actions/attest-build-provenance@v4
       with:
         subject-path: "*/pybind11*"
 
diff -Nru pybind11-3.0.1/.github/workflows/reusable-standard.yml pybind11-3.0.4/.github/workflows/reusable-standard.yml
--- pybind11-3.0.1/.github/workflows/reusable-standard.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/reusable-standard.yml	2026-04-19 06:01:51.000000000 +0300
@@ -28,12 +28,13 @@
   standard:
     name: 🧪
     runs-on: ${{ inputs.runs-on }}
+    timeout-minutes: 90
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
 
       - name: Setup Python ${{ inputs.python-version }}
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: ${{ inputs.python-version }}
           allow-prereleases: true
@@ -43,14 +44,14 @@
 
       - name: Setup Boost (Linux)
         if: runner.os == 'Linux'
-        run: sudo apt-get install libboost-dev
+        run: sudo apt-get update && sudo apt-get install -y libboost-dev
 
       - name: Setup Boost (macOS)
         if: runner.os == 'macOS'
         run: brew install boost
 
       - name: Install uv
-        uses: astral-sh/setup-uv@v6
+        uses: astral-sh/setup-uv@v8.0.0
         with:
           enable-cache: true
 
@@ -82,6 +83,7 @@
         run: cmake --build build --target pytest
 
       - name: C++ tests
+        timeout-minutes: 3
         run: cmake --build build --target cpptest
 
       - name: Interface test
diff -Nru pybind11-3.0.1/.github/workflows/tests-cibw.yml pybind11-3.0.4/.github/workflows/tests-cibw.yml
--- pybind11-3.0.1/.github/workflows/tests-cibw.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/tests-cibw.yml	2026-04-19 06:01:51.000000000 +0300
@@ -17,12 +17,12 @@
     name: Pyodide wheel
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
       with:
         submodules: true
         fetch-depth: 0
 
-    - uses: pypa/cibuildwheel@v3.1
+    - uses: pypa/cibuildwheel@v3.4
       env:
         PYODIDE_BUILD_EXPORTS: whole_archive
       with:
@@ -35,16 +35,17 @@
     strategy:
       fail-fast: false
       matrix:
-        runs-on: [macos-14, macos-13]
+        runs-on: [macos-14, macos-15-intel]
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
       with:
         submodules: true
         fetch-depth: 0
 
-    - run: brew upgrade cmake
+    # We have to uninstall first because GH is now using a local tap to build cmake<4, iOS needs cmake>=4
+    - run: brew uninstall cmake && brew install cmake
 
-    - uses: pypa/cibuildwheel@v3.1
+    - uses: pypa/cibuildwheel@v3.4
       env:
         CIBW_PLATFORM: ios
         CIBW_SKIP: cp314-*  # https://github.com/pypa/cibuildwheel/issues/2494
@@ -57,9 +58,9 @@
     strategy:
       fail-fast: false
       matrix:
-        runs-on: [macos-latest, macos-13, ubuntu-latest]
+        runs-on: [macos-latest, macos-15-intel, ubuntu-latest]
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
       with:
         submodules: true
         fetch-depth: 0
@@ -69,17 +70,7 @@
       if: contains(matrix.runs-on, 'macos')
       run: echo "CIBW_TEST_COMMAND=" >> "$GITHUB_ENV"
 
-    # https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
-    - name: Enable KVM for Android emulator
-      if: contains(matrix.runs-on, 'ubuntu')
-      run: |
-        echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
-        sudo udevadm control --reload-rules
-        sudo udevadm trigger --name-match=kvm
-
-    - run: pipx install patchelf
-
-    - uses: pypa/cibuildwheel@v3.1
+    - uses: pypa/cibuildwheel@v3.4
       env:
         CIBW_PLATFORM: android
       with:
diff -Nru pybind11-3.0.1/.github/workflows/upstream.yml pybind11-3.0.4/.github/workflows/upstream.yml
--- pybind11-3.0.1/.github/workflows/upstream.yml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.github/workflows/upstream.yml	2026-04-19 06:01:51.000000000 +0300
@@ -24,10 +24,10 @@
     if: "contains(github.event.pull_request.labels.*.name, 'python dev')"
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
 
     - name: Setup Python 3.13
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v6
       with:
         python-version: "3.13"
         allow-prereleases: true
@@ -36,7 +36,7 @@
       run: sudo apt-get install libboost-dev
 
     - name: Update CMake
-      uses: jwlawson/actions-setup-cmake@v2.0
+      uses: jwlawson/actions-setup-cmake@v2.2
 
     - name: Run pip installs
       run: |
@@ -66,6 +66,7 @@
       run: cmake --build build11 --target pytest -j 2
 
     - name: C++11 tests
+      timeout-minutes: 3
       run: cmake --build build11  --target cpptest -j 2
 
     - name: Interface test C++11
@@ -87,6 +88,7 @@
       run: cmake --build build17 --target pytest
 
     - name: C++17 tests
+      timeout-minutes: 3
       run: cmake --build build17 --target cpptest
 
     # Third build - C++17 mode with unstable ABI
diff -Nru pybind11-3.0.1/.gitignore pybind11-3.0.4/.gitignore
--- pybind11-3.0.1/.gitignore	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.gitignore	2026-04-19 06:01:51.000000000 +0300
@@ -26,6 +26,7 @@
 /.ninja_*
 /*.ninja
 /docs/.build
+__pycache__/
 *.py[co]
 *.egg-info
 *~
diff -Nru pybind11-3.0.1/include/pybind11/attr.h pybind11-3.0.4/include/pybind11/attr.h
--- pybind11-3.0.1/include/pybind11/attr.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/attr.h	2026-04-19 06:01:51.000000000 +0300
@@ -373,12 +373,12 @@
                           + (base_has_unique_ptr_holder ? "does not" : "does"));
         }
 
-        bases.append((PyObject *) base_info->type);
+        bases.append(reinterpret_cast<PyObject *>(base_info->type));
 
 #ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
         dynamic_attr |= base_info->type->tp_dictoffset != 0;
 #else
-        dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
+        dynamic_attr |= (PyType_GetFlags(base_info->type) & Py_TPFLAGS_MANAGED_DICT) != 0;
 #endif
 
         if (caster) {
@@ -703,6 +703,12 @@
 };
 
 template <typename T>
+struct is_keep_alive : std::false_type {};
+
+template <size_t Nurse, size_t Patient>
+struct is_keep_alive<keep_alive<Nurse, Patient>> : std::true_type {};
+
+template <typename T>
 using is_call_guard = is_instantiation<call_guard, T>;
 
 /// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found)
@@ -715,7 +721,9 @@
           size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
 constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
     PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs);
-    return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs;
+    return named == 0
+           || (self + named + static_cast<size_t>(has_args) + static_cast<size_t>(has_kwargs))
+                  == nargs;
 }
 
 PYBIND11_NAMESPACE_END(detail)
diff -Nru pybind11-3.0.1/include/pybind11/buffer_info.h pybind11-3.0.4/include/pybind11/buffer_info.h
--- pybind11-3.0.1/include/pybind11/buffer_info.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/buffer_info.h	2026-04-19 06:01:51.000000000 +0300
@@ -66,10 +66,11 @@
                 bool readonly = false)
         : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
           shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
-        if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) {
+        if (ndim != static_cast<ssize_t>(shape.size())
+            || ndim != static_cast<ssize_t>(strides.size())) {
             pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
         }
-        for (size_t i = 0; i < (size_t) ndim; ++i) {
+        for (size_t i = 0; i < static_cast<size_t>(ndim); ++i) {
             size *= shape[i];
         }
     }
@@ -195,7 +196,7 @@
 template <typename T>
 struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
     static bool compare(const buffer_info &b) {
-        return (size_t) b.itemsize == sizeof(T)
+        return static_cast<size_t>(b.itemsize) == sizeof(T)
                && (b.format == format_descriptor<T>::value
                    || ((sizeof(T) == sizeof(long))
                        && b.format == (std::is_unsigned<T>::value ? "L" : "l"))
diff -Nru pybind11-3.0.1/include/pybind11/cast.h pybind11-3.0.4/include/pybind11/cast.h
--- pybind11-3.0.1/include/pybind11/cast.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/cast.h	2026-04-19 06:01:51.000000000 +0300
@@ -10,8 +10,10 @@
 
 #pragma once
 
+#include "detail/argument_vector.h"
 #include "detail/common.h"
 #include "detail/descr.h"
+#include "detail/holder_caster_foreign_helpers.h"
 #include "detail/native_enum_data.h"
 #include "detail/type_caster_base.h"
 #include "detail/typeid.h"
@@ -92,37 +94,37 @@
             if (!underlying_caster.load(src.attr("value"), convert)) {
                 pybind11_fail("native_enum internal consistency failure.");
             }
-            value = static_cast<EnumType>(static_cast<Underlying>(underlying_caster));
+            native_value = static_cast<EnumType>(static_cast<Underlying>(underlying_caster));
+            native_loaded = true;
             return true;
         }
-        if (!pybind11_enum_) {
-            pybind11_enum_.reset(new type_caster_base<EnumType>());
+
+        type_caster_base<EnumType> legacy_caster;
+        if (legacy_caster.load(src, convert)) {
+            legacy_ptr = static_cast<EnumType *>(legacy_caster);
+            return true;
         }
-        return pybind11_enum_->load(src, convert);
+        return false;
     }
 
     template <typename T>
     using cast_op_type = detail::cast_op_type<T>;
 
     // NOLINTNEXTLINE(google-explicit-constructor)
-    operator EnumType *() {
-        if (!pybind11_enum_) {
-            return &value;
-        }
-        return pybind11_enum_->operator EnumType *();
-    }
+    operator EnumType *() { return native_loaded ? &native_value : legacy_ptr; }
 
     // NOLINTNEXTLINE(google-explicit-constructor)
     operator EnumType &() {
-        if (!pybind11_enum_) {
-            return value;
+        if (!native_loaded && !legacy_ptr) {
+            throw reference_cast_error();
         }
-        return pybind11_enum_->operator EnumType &();
+        return native_loaded ? native_value : *legacy_ptr;
     }
 
 private:
-    std::unique_ptr<type_caster_base<EnumType>> pybind11_enum_;
-    EnumType value;
+    EnumType native_value; // if loading a py::native_enum
+    bool native_loaded = false;
+    EnumType *legacy_ptr = nullptr; // if loading a py::enum_
 };
 
 template <typename EnumType, typename SFINAE = void>
@@ -197,8 +199,7 @@
     template <typename T_,                                                                        \
               ::pybind11::detail::enable_if_t<                                                    \
                   std::is_same<type, ::pybind11::detail::remove_cv_t<T_>>::value,                 \
-                  int>                                                                            \
-              = 0>                                                                                \
+                  int> = 0>                                                                       \
     static ::pybind11::handle cast(                                                               \
         T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) {             \
         if (!src)                                                                                 \
@@ -345,9 +346,12 @@
         return PyLong_FromUnsignedLongLong((unsigned long long) src);
     }
 
-    PYBIND11_TYPE_CASTER(T,
-                         io_name<std::is_integral<T>::value>(
-                             "typing.SupportsInt", "int", "typing.SupportsFloat", "float"));
+    PYBIND11_TYPE_CASTER(
+        T,
+        io_name<std::is_integral<T>::value>("typing.SupportsInt | typing.SupportsIndex",
+                                            "int",
+                                            "typing.SupportsFloat | typing.SupportsIndex",
+                                            "float"));
 };
 
 template <typename T>
@@ -389,7 +393,8 @@
         }
 
         /* Check if this is a C++ type */
-        const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr());
+        const auto &bases
+            = all_type_info(reinterpret_cast<PyTypeObject *>(type::handle_of(h).ptr()));
         if (bases.size() == 1) { // Only allowing loading from a single-value type
             value = values_and_holders(reinterpret_cast<instance *>(h.ptr())).begin()->value_ptr();
             return true;
@@ -470,7 +475,7 @@
 
 private:
     // Test if an object is a NumPy boolean (without fetching the type).
-    static inline bool is_numpy_bool(handle object) {
+    static bool is_numpy_bool(handle object) {
         const char *type_name = Py_TYPE(object.ptr())->tp_name;
         // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support
         return std::strcmp("numpy.bool", type_name) == 0
@@ -536,7 +541,7 @@
 
         const auto *buffer
             = reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
-        size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
+        size_t length = static_cast<size_t>(PYBIND11_BYTES_SIZE(utfNbytes.ptr())) / sizeof(CharT);
         // Skip BOM for UTF-16/32
         if (UTF_N > 8) {
             buffer++;
@@ -906,6 +911,10 @@
         }
     }
 
+    bool set_foreign_holder(handle src) {
+        return holder_caster_foreign_helpers::set_foreign_holder(src, (type *) value, &holder);
+    }
+
     void load_value(value_and_holder &&v_h) {
         if (v_h.holder_constructed()) {
             value = v_h.value_ptr();
@@ -976,7 +985,7 @@
     }
 
     explicit operator std::shared_ptr<type> *() {
-        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+        if (sh_load_helper.was_populated) {
             pybind11_fail("Passing `std::shared_ptr<T> *` from Python to C++ is not supported "
                           "(inherently unsafe).");
         }
@@ -984,14 +993,14 @@
     }
 
     explicit operator std::shared_ptr<type> &() {
-        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+        if (sh_load_helper.was_populated) {
             shared_ptr_storage = sh_load_helper.load_as_shared_ptr(typeinfo, value);
         }
         return shared_ptr_storage;
     }
 
     std::weak_ptr<type> potentially_slicing_weak_ptr() {
-        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+        if (sh_load_helper.was_populated) {
             // Reusing shared_ptr code to minimize code complexity.
             shared_ptr_storage
                 = sh_load_helper.load_as_shared_ptr(typeinfo,
@@ -1005,15 +1014,24 @@
     static handle
     cast(const std::shared_ptr<type> &src, return_value_policy policy, handle parent) {
         const auto *ptr = src.get();
-        auto st = type_caster_base<type>::src_and_type(ptr);
-        if (st.second == nullptr) {
-            return handle(); // no type info: error will be set already
-        }
-        if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
+        typename type_caster_base<type>::cast_sources srcs{ptr};
+        if (srcs.creates_smart_holder()) {
             return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
-                src, policy, parent, st);
+                src, policy, parent, srcs.result);
         }
-        return type_caster_base<type>::cast_holder(ptr, &src);
+
+        auto *tinfo = srcs.result.tinfo;
+        if (tinfo != nullptr && tinfo->holder_enum_v == holder_enum_t::std_shared_ptr) {
+            return type_caster_base<type>::cast_holder(srcs, &src);
+        }
+
+        if (parent) {
+            return type_caster_generic::cast_non_owning(
+                srcs, return_value_policy::reference_internal, parent);
+        }
+
+        throw cast_error("Unable to convert std::shared_ptr<T> to Python when the bound type "
+                         "does not use std::shared_ptr or py::smart_holder as its holder type");
     }
 
     // This function will succeed even if the `responsible_parent` does not own the
@@ -1040,6 +1058,11 @@
         }
     }
 
+    bool set_foreign_holder(handle src) {
+        return holder_caster_foreign_helpers::set_foreign_holder(
+            src, (type *) value, &shared_ptr_storage);
+    }
+
     void load_value(value_and_holder &&v_h) {
         if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
             sh_load_helper.loaded_v_h = v_h;
@@ -1077,6 +1100,7 @@
                 value = cast.second(sub_caster.value);
                 if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
                     sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
+                    sh_load_helper.was_populated = true;
                 } else {
                     shared_ptr_storage
                         = std::shared_ptr<type>(sub_caster.shared_ptr_storage, (type *) value);
@@ -1183,21 +1207,12 @@
     static handle
     cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
         auto *ptr = src.get();
-        auto st = type_caster_base<type>::src_and_type(ptr);
-        if (st.second == nullptr) {
-            return handle(); // no type info: error will be set already
-        }
-        if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
+        typename type_caster_base<type>::cast_sources srcs{ptr};
+        if (srcs.creates_smart_holder()) {
             return smart_holder_type_caster_support::smart_holder_from_unique_ptr(
-                std::move(src), policy, parent, st);
+                std::move(src), policy, parent, srcs.result);
         }
-        return type_caster_generic::cast(st.first,
-                                         return_value_policy::take_ownership,
-                                         {},
-                                         st.second,
-                                         nullptr,
-                                         nullptr,
-                                         std::addressof(src));
+        return type_caster_base<type>::cast_holder(srcs, &src);
     }
 
     static handle
@@ -1223,6 +1238,12 @@
         return false;
     }
 
+    bool set_foreign_holder(handle) {
+        throw cast_error("Foreign instance cannot be converted to std::unique_ptr "
+                         "because we don't know how to make it relinquish "
+                         "ownership");
+    }
+
     void load_value(value_and_holder &&v_h) {
         if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
             sh_load_helper.loaded_v_h = v_h;
@@ -1281,6 +1302,7 @@
                 value = cast.second(sub_caster.value);
                 if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
                     sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
+                    sh_load_helper.was_populated = true;
                 } else {
                     pybind11_fail("Expected to be UNREACHABLE: " __FILE__
                                   ":" PYBIND11_TOSTRING(__LINE__));
@@ -1400,7 +1422,7 @@
 };
 template <>
 struct handle_type_name<int_> {
-    static constexpr auto name = io_name("typing.SupportsInt", "int");
+    static constexpr auto name = const_name("int");
 };
 template <>
 struct handle_type_name<iterable> {
@@ -1412,7 +1434,7 @@
 };
 template <>
 struct handle_type_name<float_> {
-    static constexpr auto name = io_name("typing.SupportsFloat", "float");
+    static constexpr auto name = const_name("float");
 };
 template <>
 struct handle_type_name<function> {
@@ -1458,21 +1480,24 @@
 struct handle_type_name<weakref> {
     static constexpr auto name = const_name("weakref.ReferenceType");
 };
+// args/Args/kwargs/KWArgs have name as well as typehint included
 template <>
 struct handle_type_name<args> {
-    static constexpr auto name = const_name("*args");
+    static constexpr auto name = io_name("*args", "tuple");
 };
 template <typename T>
 struct handle_type_name<Args<T>> {
-    static constexpr auto name = const_name("*args: ") + make_caster<T>::name;
+    static constexpr auto name
+        = io_name("*args: ", "tuple[") + make_caster<T>::name + io_name("", ", ...]");
 };
 template <>
 struct handle_type_name<kwargs> {
-    static constexpr auto name = const_name("**kwargs");
+    static constexpr auto name = io_name("**kwargs", "dict[str, typing.Any]");
 };
 template <typename T>
 struct handle_type_name<KWArgs<T>> {
-    static constexpr auto name = const_name("**kwargs: ") + make_caster<T>::name;
+    static constexpr auto name
+        = io_name("**kwargs: ", "dict[str, ") + make_caster<T>::name + io_name("", "]");
 };
 template <>
 struct handle_type_name<obj_attr_accessor> {
@@ -1533,6 +1558,21 @@
 template <typename T>
 class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caster<T> {};
 
+template <>
+class type_caster<float_> : public pyobject_caster<float_> {
+public:
+    bool load(handle src, bool /* convert */) {
+        if (isinstance<float_>(src)) {
+            value = reinterpret_borrow<float_>(src);
+        } else if (isinstance<int_>(src)) {
+            value = float_(reinterpret_borrow<int_>(src));
+        } else {
+            return false;
+        }
+        return true;
+    }
+};
+
 // Our conditions for enabling moving are quite restrictive:
 // At compile time:
 // - T needs to be a non-const, non-pointer, non-reference type
@@ -1633,8 +1673,7 @@
 template <typename T,
           detail::enable_if_t<!detail::is_pyobject<T>::value
                                   && !detail::is_same_ignoring_cvref<T, PyObject *>::value,
-                              int>
-          = 0>
+                              int> = 0>
 T cast(const handle &handle) {
     using namespace detail;
     constexpr bool is_enum_cast = type_uses_type_caster_enum_type<intrinsic_t<T>>::value;
@@ -1669,8 +1708,7 @@
           typename Handle,
           detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
                                   && detail::is_same_ignoring_cvref<Handle, handle>::value,
-                              int>
-          = 0>
+                              int> = 0>
 T cast(Handle &&handle) {
     return handle.inc_ref().ptr();
 }
@@ -1679,8 +1717,7 @@
           typename Object,
           detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
                                   && detail::is_same_ignoring_cvref<Object, object>::value,
-                              int>
-          = 0>
+                              int> = 0>
 T cast(Object &&obj) {
     return obj.release().ptr();
 }
@@ -1883,13 +1920,20 @@
 }
 #endif
 
+namespace typing {
+template <typename... Types>
+class Tuple : public tuple {
+    using tuple::tuple;
+};
+} // namespace typing
+
 template <return_value_policy policy = return_value_policy::automatic_reference>
-tuple make_tuple() {
+typing::Tuple<> make_tuple() {
     return tuple(0);
 }
 
 template <return_value_policy policy = return_value_policy::automatic_reference, typename... Args>
-tuple make_tuple(Args &&...args_) {
+typing::Tuple<Args...> make_tuple(Args &&...args_) {
     constexpr size_t size = sizeof...(Args);
     std::array<object, size> args{{reinterpret_steal<object>(
         detail::make_caster<Args>::cast(std::forward<Args>(args_), policy, nullptr))...}};
@@ -1908,7 +1952,12 @@
     for (auto &arg_value : args) {
         PyTuple_SET_ITEM(result.ptr(), counter++, arg_value.release().ptr());
     }
+    PYBIND11_WARNING_PUSH
+#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
+    PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move")
+#endif
     return result;
+    PYBIND11_WARNING_POP
 }
 
 /// \ingroup annotations
@@ -2037,6 +2086,9 @@
 // forward declaration (definition in attr.h)
 struct function_record;
 
+/// Inline size chosen mostly arbitrarily.
+constexpr std::size_t arg_vector_small_size = 6;
+
 /// Internal data associated with a single function call
 struct function_call {
     function_call(const function_record &f, handle p); // Implementation in attr.h
@@ -2045,10 +2097,10 @@
     const function_record &func;
 
     /// Arguments passed to the function:
-    std::vector<handle> args;
+    argument_vector<arg_vector_small_size> args;
 
     /// The `convert` value the arguments should be loaded with
-    std::vector<bool> args_convert;
+    args_convert_vector<arg_vector_small_size> args_convert;
 
     /// Extra references for the optional `py::args` and/or `py::kwargs` arguments (which, if
     /// present, are also in `args` but without a reference).
@@ -2118,6 +2170,11 @@
 
     template <size_t... Is>
     bool load_impl_sequence(function_call &call, index_sequence<Is...>) {
+        PYBIND11_WARNING_PUSH
+#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 13
+        // Work around a GCC -Warray-bounds false positive in argument_vector usage.
+        PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds")
+#endif
 #ifdef __cpp_fold_expressions
         if ((... || !std::get<Is>(argcasters).load(call.args[Is], call.args_convert[Is]))) {
             return false;
@@ -2129,6 +2186,7 @@
             }
         }
 #endif
+        PYBIND11_WARNING_POP
         return true;
     }
 
@@ -2140,86 +2198,126 @@
     std::tuple<make_caster<Args>...> argcasters;
 };
 
-/// Helper class which collects only positional arguments for a Python function call.
-/// A fancier version below can collect any argument, but this one is optimal for simple calls.
+// [workaround(intel)] Separate function required here
+// We need to put this into a separate function because the Intel compiler
+// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
+// (tested with ICC 2021.1 Beta 20200827).
+template <typename... Args>
+constexpr bool args_has_keyword_or_ds() {
+    return any_of<is_keyword_or_ds<Args>...>::value;
+}
+
+/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
 template <return_value_policy policy>
-class simple_collector {
+class unpacking_collector {
 public:
     template <typename... Ts>
-    explicit simple_collector(Ts &&...values)
-        : m_args(pybind11::make_tuple<policy>(std::forward<Ts>(values)...)) {}
+    explicit unpacking_collector(Ts &&...values)
+        : m_names(reinterpret_steal<tuple>(
+              handle())) // initialize to null to avoid useless allocation of 0-length tuple
+    {
+        /*
+        Python can sometimes utilize an extra space before the arguments to prepend `self`.
+        This is important enough that there is a special flag for it:
+        PY_VECTORCALL_ARGUMENTS_OFFSET.
+        All we have to do is allocate an extra space at the beginning of this array, and set the
+        flag. Note that the extra space is not passed directly in to vectorcall.
+        */
+        m_args.reserve(sizeof...(values) + 1);
+        m_args.push_back_null();
+
+        if (args_has_keyword_or_ds<Ts...>()) {
+            list names_list;
+
+            // collect_arguments guarantees this can't be constructed with kwargs before the last
+            // positional so we don't need to worry about Ts... being in anything but normal python
+            // order.
+            using expander = int[];
+            (void) expander{0, (process(names_list, std::forward<Ts>(values)), 0)...};
 
-    const tuple &args() const & { return m_args; }
-    dict kwargs() const { return {}; }
+            m_names = reinterpret_steal<tuple>(PyList_AsTuple(names_list.ptr()));
+        } else {
+            auto not_used
+                = reinterpret_steal<list>(handle()); // initialize as null (to avoid an allocation)
 
-    tuple args() && { return std::move(m_args); }
+            using expander = int[];
+            (void) expander{0, (process(not_used, std::forward<Ts>(values)), 0)...};
+        }
+    }
 
     /// Call a Python function and pass the collected arguments
     object call(PyObject *ptr) const {
-        PyObject *result = PyObject_CallObject(ptr, m_args.ptr());
+        size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
+        if (m_names) {
+            nargs -= m_names.size();
+        }
+        PyObject *result =
+#if PY_VERSION_HEX >= 0x03090000
+            PyObject_Vectorcall(
+#else
+            _PyObject_Vectorcall(
+#endif
+                ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
         if (!result) {
             throw error_already_set();
         }
         return reinterpret_steal<object>(result);
     }
 
-private:
-    tuple m_args;
-};
-
-/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
-template <return_value_policy policy>
-class unpacking_collector {
-public:
-    template <typename... Ts>
-    explicit unpacking_collector(Ts &&...values) {
-        // Tuples aren't (easily) resizable so a list is needed for collection,
-        // but the actual function call strictly requires a tuple.
-        auto args_list = list();
-        using expander = int[];
-        (void) expander{0, (process(args_list, std::forward<Ts>(values)), 0)...};
-
-        m_args = std::move(args_list);
+    tuple args() const {
+        size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
+        if (m_names) {
+            nargs -= m_names.size();
+        }
+        tuple val(nargs);
+        for (size_t i = 0; i < nargs; ++i) {
+            // +1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
+            val[i] = reinterpret_borrow<object>(m_args[i + 1]);
+        }
+        return val;
     }
 
-    const tuple &args() const & { return m_args; }
-    const dict &kwargs() const & { return m_kwargs; }
-
-    tuple args() && { return std::move(m_args); }
-    dict kwargs() && { return std::move(m_kwargs); }
-
-    /// Call a Python function and pass the collected arguments
-    object call(PyObject *ptr) const {
-        PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr());
-        if (!result) {
-            throw error_already_set();
+    dict kwargs() const {
+        dict val;
+        if (m_names) {
+            size_t offset = m_args.size() - m_names.size();
+            for (size_t i = 0; i < m_names.size(); ++i, ++offset) {
+                val[m_names[i]] = reinterpret_borrow<object>(m_args[offset]);
+            }
         }
-        return reinterpret_steal<object>(result);
+        return val;
     }
 
 private:
+    // normal argument, possibly needing conversion
     template <typename T>
-    void process(list &args_list, T &&x) {
-        auto o = reinterpret_steal<object>(
-            detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
-        if (!o) {
+    void process(list & /*names_list*/, T &&x) {
+        handle h = detail::make_caster<T>::cast(std::forward<T>(x), policy, {});
+        if (!h) {
 #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
-            throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
+            throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1));
 #else
-            throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
+            throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1),
                                                         type_id<T>());
 #endif
         }
-        args_list.append(std::move(o));
+        m_args.push_back_steal(h.ptr()); // cast returns a new reference
     }
 
-    void process(list &args_list, detail::args_proxy ap) {
+    // * unpacking
+    void process(list & /*names_list*/, detail::args_proxy ap) {
+        if (!ap) {
+            return;
+        }
         for (auto a : ap) {
-            args_list.append(a);
+            m_args.push_back_borrow(a.ptr());
         }
     }
 
-    void process(list & /*args_list*/, arg_v a) {
+    // named argument
+    // NOLINTNEXTLINE(performance-unnecessary-value-param)
+    void process(list &names_list, arg_v a) {
+        assert(names_list);
         if (!a.name) {
 #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
             nameless_argument_error();
@@ -2227,7 +2325,8 @@
             nameless_argument_error(a.type);
 #endif
         }
-        if (m_kwargs.contains(a.name)) {
+        auto name = str(a.name);
+        if (names_list.contains(name)) {
 #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
             multiple_values_error();
 #else
@@ -2241,22 +2340,27 @@
             throw cast_error_unable_to_convert_call_arg(a.name, a.type);
 #endif
         }
-        m_kwargs[a.name] = std::move(a.value);
+        names_list.append(std::move(name));
+        m_args.push_back_borrow(a.value.ptr());
     }
 
-    void process(list & /*args_list*/, detail::kwargs_proxy kp) {
+    // ** unpacking
+    void process(list &names_list, detail::kwargs_proxy kp) {
         if (!kp) {
             return;
         }
-        for (auto k : reinterpret_borrow<dict>(kp)) {
-            if (m_kwargs.contains(k.first)) {
+        assert(names_list);
+        for (auto &&k : reinterpret_borrow<dict>(kp)) {
+            auto name = str(k.first);
+            if (names_list.contains(name)) {
 #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
                 multiple_values_error();
 #else
-                multiple_values_error(str(k.first));
+                multiple_values_error(name);
 #endif
             }
-            m_kwargs[k.first] = k.second;
+            names_list.append(std::move(name));
+            m_args.push_back_borrow(k.second.ptr());
         }
     }
 
@@ -2282,39 +2386,20 @@
     }
 
 private:
-    tuple m_args;
-    dict m_kwargs;
+    ref_small_vector<arg_vector_small_size> m_args;
+    tuple m_names;
 };
 
-// [workaround(intel)] Separate function required here
-// We need to put this into a separate function because the Intel compiler
-// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
-// (tested with ICC 2021.1 Beta 20200827).
-template <typename... Args>
-constexpr bool args_are_all_positional() {
-    return all_of<is_positional<Args>...>::value;
-}
-
-/// Collect only positional arguments for a Python function call
-template <return_value_policy policy,
-          typename... Args,
-          typename = enable_if_t<args_are_all_positional<Args...>()>>
-simple_collector<policy> collect_arguments(Args &&...args) {
-    return simple_collector<policy>(std::forward<Args>(args)...);
-}
-
-/// Collect all arguments, including keywords and unpacking (only instantiated when needed)
-template <return_value_policy policy,
-          typename... Args,
-          typename = enable_if_t<!args_are_all_positional<Args...>()>>
+/// Collect all arguments, including keywords and unpacking
+template <return_value_policy policy, typename... Args>
 unpacking_collector<policy> collect_arguments(Args &&...args) {
     // Following argument order rules for generalized unpacking according to PEP 448
-    static_assert(constexpr_last<is_positional, Args...>()
-                          < constexpr_first<is_keyword_or_ds, Args...>()
-                      && constexpr_last<is_s_unpacking, Args...>()
-                             < constexpr_first<is_ds_unpacking, Args...>(),
-                  "Invalid function call: positional args must precede keywords and ** unpacking; "
-                  "* unpacking must precede ** unpacking");
+    static_assert(
+        constexpr_last<is_positional, Args...>() < constexpr_first<is_keyword_or_ds, Args...>(),
+        "Invalid function call: positional args must precede keywords and */** unpacking;");
+    static_assert(constexpr_last<is_s_unpacking, Args...>()
+                      < constexpr_first<is_ds_unpacking, Args...>(),
+                  "Invalid function call: * unpacking must precede ** unpacking");
     return unpacking_collector<policy>(std::forward<Args>(args)...);
 }
 
diff -Nru pybind11-3.0.1/include/pybind11/chrono.h pybind11-3.0.4/include/pybind11/chrono.h
--- pybind11-3.0.1/include/pybind11/chrono.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/chrono.h	2026-04-19 06:01:51.000000000 +0300
@@ -64,8 +64,7 @@
         return src;
     }
     static const std::chrono::duration<rep, period> &
-    get_duration(const std::chrono::duration<rep, period> &&)
-        = delete;
+    get_duration(const std::chrono::duration<rep, period> &&) = delete;
 
     // If this is a time_point get the time_since_epoch
     template <typename Clock>
diff -Nru pybind11-3.0.1/include/pybind11/complex.h pybind11-3.0.4/include/pybind11/complex.h
--- pybind11-3.0.1/include/pybind11/complex.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/complex.h	2026-04-19 06:01:51.000000000 +0300
@@ -54,7 +54,23 @@
         if (!convert && !PyComplex_Check(src.ptr())) {
             return false;
         }
-        Py_complex result = PyComplex_AsCComplex(src.ptr());
+        handle src_or_index = src;
+        // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls.
+        // The same logic is used in numeric_caster for ints and floats
+#if defined(PYPY_VERSION)
+        object index;
+        if (PYBIND11_INDEX_CHECK(src.ptr())) {
+            index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
+            if (!index) {
+                PyErr_Clear();
+                if (!convert)
+                    return false;
+            } else {
+                src_or_index = index;
+            }
+        }
+#endif
+        Py_complex result = PyComplex_AsCComplex(src_or_index.ptr());
         if (result.real == -1.0 && PyErr_Occurred()) {
             PyErr_Clear();
             return false;
@@ -68,7 +84,10 @@
         return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
     }
 
-    PYBIND11_TYPE_CASTER(std::complex<T>, const_name("complex"));
+    PYBIND11_TYPE_CASTER(
+        std::complex<T>,
+        io_name("typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex",
+                "complex"));
 };
 PYBIND11_NAMESPACE_END(detail)
 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/detail/argument_vector.h pybind11-3.0.4/include/pybind11/detail/argument_vector.h
--- pybind11-3.0.1/include/pybind11/detail/argument_vector.h	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/include/pybind11/detail/argument_vector.h	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,417 @@
+/*
+    pybind11/detail/argument_vector.h: small_vector-like containers to
+    avoid heap allocation of arguments during function call dispatch.
+
+    Copyright (c) Meta Platforms, Inc. and affiliates.
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include <pybind11/pytypes.h>
+
+#include "common.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+
+PYBIND11_WARNING_DISABLE_MSVC(4127)
+
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+// Shared implementation utility for our small_vector-like containers.
+// We support C++11 and C++14, so we cannot use
+// std::variant. Union with the tag packed next to the inline
+// array's size is smaller anyway, allowing 1 extra handle of
+// inline storage for free. Compare the layouts (1 line per
+// size_t/void*, assuming a 64-bit machine):
+// With variant, total is N + 2 for N >= 2:
+// - variant tag (cannot be packed with the array size)
+// - array size (or first pointer of 3 in std::vector)
+// - N pointers of inline storage (or 2 remaining pointers of std::vector)
+// Custom union, total is N + 1 for N >= 3:
+// - variant tag & array size if applicable
+// - N pointers of inline storage (or 3 pointers of std::vector)
+//
+// NOTE: this is a low-level representational convenience; the two
+// use cases of this union are materially different and in particular
+// have different semantics for inline_array::size. All that is being
+// shared is the memory management behavior.
+template <typename ArrayT, std::size_t InlineSize, typename VectorT = ArrayT>
+union inline_array_or_vector {
+    struct inline_array {
+        bool is_inline = true;
+        std::uint32_t size = 0;
+        std::array<ArrayT, InlineSize> arr;
+    };
+    struct heap_vector {
+        bool is_inline = false;
+        std::vector<VectorT> vec;
+
+        heap_vector() = default;
+        heap_vector(std::size_t count, VectorT value) : vec(count, value) {}
+    };
+
+    inline_array iarray;
+    heap_vector hvector;
+
+    inline_array_or_vector() : iarray() {}
+
+    ~inline_array_or_vector() {
+        if (is_inline()) {
+            iarray.~inline_array();
+        } else {
+            hvector.~heap_vector();
+        }
+    }
+
+    // Disable copy ctor and assignment.
+    inline_array_or_vector(const inline_array_or_vector &) = delete;
+    inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
+
+    inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
+        if (rhs.is_inline()) {
+            new (&iarray) inline_array(std::move(rhs.iarray));
+        } else {
+            new (&hvector) heap_vector(std::move(rhs.hvector));
+        }
+        assert(is_inline() == rhs.is_inline());
+    }
+
+    inline_array_or_vector &operator=(inline_array_or_vector &&rhs) noexcept {
+        if (this == &rhs) {
+            return *this;
+        }
+
+        if (is_inline()) {
+            iarray.~inline_array();
+        } else {
+            hvector.~heap_vector();
+        }
+
+        if (rhs.is_inline()) {
+            new (&iarray) inline_array(std::move(rhs.iarray));
+        } else {
+            new (&hvector) heap_vector(std::move(rhs.hvector));
+        }
+        return *this;
+    }
+
+    bool is_inline() const {
+        // It is undefined behavior to access the inactive member of a
+        // union directly. However, it is well-defined to reinterpret_cast any
+        // pointer into a pointer to char and examine it as an array
+        // of bytes. See
+        // https://dev-discuss.pytorch.org/t/unionizing-for-profit-how-to-exploit-the-power-of-unions-in-c/444#the-memcpy-loophole-4
+        bool result = false;
+        static_assert(offsetof(inline_array, is_inline) == 0,
+                      "untagged union implementation relies on this");
+        static_assert(offsetof(heap_vector, is_inline) == 0,
+                      "untagged union implementation relies on this");
+        std::memcpy(&result, reinterpret_cast<const char *>(this), sizeof(bool));
+        return result;
+    }
+};
+
+template <typename T, std::size_t InlineSize>
+struct small_vector {
+public:
+    small_vector() = default;
+
+    // Disable copy ctor and assignment.
+    small_vector(const small_vector &) = delete;
+    small_vector &operator=(const small_vector &) = delete;
+    small_vector(small_vector &&) noexcept = default;
+    small_vector &operator=(small_vector &&) noexcept = default;
+
+    std::size_t size() const {
+        if (is_inline()) {
+            return m_repr.iarray.size;
+        }
+        return m_repr.hvector.vec.size();
+    }
+
+    T const *data() const {
+        if (is_inline()) {
+            return m_repr.iarray.arr.data();
+        }
+        return m_repr.hvector.vec.data();
+    }
+
+    T &operator[](std::size_t idx) {
+        assert(idx < size());
+        if (is_inline()) {
+            return m_repr.iarray.arr[idx];
+        }
+        return m_repr.hvector.vec[idx];
+    }
+
+    T const &operator[](std::size_t idx) const {
+        assert(idx < size());
+        if (is_inline()) {
+            return m_repr.iarray.arr[idx];
+        }
+        return m_repr.hvector.vec[idx];
+    }
+
+    void push_back(const T &x) { emplace_back(x); }
+
+    void push_back(T &&x) { emplace_back(std::move(x)); }
+
+    template <typename... Args>
+    void emplace_back(Args &&...x) {
+        if (is_inline()) {
+            auto &ha = m_repr.iarray;
+            if (ha.size == InlineSize) {
+                move_to_heap_vector_with_reserved_size(InlineSize + 1);
+                m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
+            } else {
+                ha.arr[ha.size++] = T(std::forward<Args>(x)...);
+            }
+        } else {
+            m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
+        }
+    }
+
+    void reserve(std::size_t sz) {
+        if (is_inline()) {
+            if (sz > InlineSize) {
+                move_to_heap_vector_with_reserved_size(sz);
+            }
+        } else {
+            reserve_slow_path(sz);
+        }
+    }
+
+private:
+    using repr_type = inline_array_or_vector<T, InlineSize>;
+    repr_type m_repr;
+
+    PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
+        assert(is_inline());
+        auto &ha = m_repr.iarray;
+        using heap_vector = typename repr_type::heap_vector;
+        heap_vector hv;
+        hv.vec.reserve(reserved_size);
+        static_assert(std::is_nothrow_move_constructible<T>::value,
+                      "this conversion is not exception safe");
+        static_assert(std::is_nothrow_move_constructible<heap_vector>::value,
+                      "this conversion is not exception safe");
+        std::move(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
+        new (&m_repr.hvector) heap_vector(std::move(hv));
+    }
+
+    PYBIND11_NOINLINE void reserve_slow_path(std::size_t sz) { m_repr.hvector.vec.reserve(sz); }
+
+    bool is_inline() const { return m_repr.is_inline(); }
+};
+
+// Container to avoid heap allocation for kRequestedInlineSize or fewer booleans.
+template <std::size_t kRequestedInlineSize>
+struct small_vector<bool, kRequestedInlineSize> {
+private:
+public:
+    small_vector() = default;
+
+    // Disable copy ctor and assignment.
+    small_vector(const small_vector &) = delete;
+    small_vector &operator=(const small_vector &) = delete;
+    small_vector(small_vector &&) noexcept = default;
+    small_vector &operator=(small_vector &&) noexcept = default;
+
+    small_vector(std::size_t count, bool value) {
+        if (count > kInlineSize) {
+            new (&m_repr.hvector) typename repr_type::heap_vector(count, value);
+        } else {
+            auto &inline_arr = m_repr.iarray;
+            inline_arr.arr.fill(value ? static_cast<std::size_t>(-1) : 0);
+            inline_arr.size = static_cast<decltype(inline_arr.size)>(count);
+        }
+    }
+
+    std::size_t size() const {
+        if (is_inline()) {
+            return m_repr.iarray.size;
+        }
+        return m_repr.hvector.vec.size();
+    }
+
+    void reserve(std::size_t sz) {
+        if (is_inline()) {
+            if (sz > kInlineSize) {
+                move_to_heap_vector_with_reserved_size(sz);
+            }
+        } else {
+            m_repr.hvector.vec.reserve(sz);
+        }
+    }
+
+    bool operator[](std::size_t idx) const {
+        if (is_inline()) {
+            return inline_index(idx);
+        }
+        assert(idx < m_repr.hvector.vec.size());
+        return m_repr.hvector.vec[idx];
+    }
+
+    void push_back(bool b) {
+        if (is_inline()) {
+            auto &ha = m_repr.iarray;
+            if (ha.size == kInlineSize) {
+                move_to_heap_vector_with_reserved_size(kInlineSize + 1);
+                push_back_slow_path(b);
+            } else {
+                assert(ha.size < kInlineSize);
+                const auto wbi = word_and_bit_index(ha.size++);
+                assert(wbi.word < kWords);
+                assert(wbi.bit < kBitsPerWord);
+                if (b) {
+                    ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
+                } else {
+                    ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
+                }
+                assert(operator[](ha.size - 1) == b);
+            }
+        } else {
+            push_back_slow_path(b);
+        }
+    }
+
+    void set(std::size_t idx, bool value = true) {
+        if (is_inline()) {
+            auto &ha = m_repr.iarray;
+            assert(ha.size < kInlineSize);
+            const auto wbi = word_and_bit_index(idx);
+            assert(wbi.word < kWords);
+            assert(wbi.bit < kBitsPerWord);
+            if (value) {
+                ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
+            } else {
+                ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
+            }
+        } else {
+            m_repr.hvector.vec[idx] = value;
+        }
+    }
+
+    void swap(small_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
+
+private:
+    struct WordAndBitIndex {
+        std::size_t word;
+        std::size_t bit;
+    };
+
+    static WordAndBitIndex word_and_bit_index(std::size_t idx) {
+        return WordAndBitIndex{idx / kBitsPerWord, idx % kBitsPerWord};
+    }
+
+    bool inline_index(std::size_t idx) const {
+        const auto wbi = word_and_bit_index(idx);
+        assert(wbi.word < kWords);
+        assert(wbi.bit < kBitsPerWord);
+        return m_repr.iarray.arr[wbi.word] & (static_cast<std::size_t>(1) << wbi.bit);
+    }
+
+    PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
+        auto &inline_arr = m_repr.iarray;
+        using heap_vector = typename repr_type::heap_vector;
+        heap_vector hv;
+        hv.vec.reserve(reserved_size);
+        for (std::size_t ii = 0; ii < inline_arr.size; ++ii) {
+            hv.vec.push_back(inline_index(ii));
+        }
+        new (&m_repr.hvector) heap_vector(std::move(hv));
+    }
+
+    PYBIND11_NOINLINE void push_back_slow_path(bool b) { m_repr.hvector.vec.push_back(b); }
+
+    static constexpr auto kBitsPerWord = 8 * sizeof(std::size_t);
+    static constexpr auto kWords = (kRequestedInlineSize + kBitsPerWord - 1) / kBitsPerWord;
+    static constexpr auto kInlineSize = kWords * kBitsPerWord;
+
+    using repr_type = inline_array_or_vector<std::size_t, kWords, bool>;
+    repr_type m_repr;
+
+    bool is_inline() const { return m_repr.is_inline(); }
+};
+
+// Container to avoid heap allocation for N or fewer arguments.
+template <size_t N>
+using argument_vector = small_vector<handle, N>;
+
+// Container to avoid heap allocation for N or fewer booleans.
+template <size_t N>
+using args_convert_vector = small_vector<bool, N>;
+
+/// A small_vector of PyObject* that holds references and releases them on destruction.
+/// This provides explicit ownership semantics without relying on py::object's
+/// destructor, and avoids the need for reinterpret_cast when passing to vectorcall.
+template <std::size_t InlineSize>
+class ref_small_vector {
+public:
+    ref_small_vector() = default;
+
+    ~ref_small_vector() {
+        for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
+            Py_XDECREF(m_ptrs[i]);
+        }
+    }
+
+    // Disable copy (prevent accidental double-decref)
+    ref_small_vector(const ref_small_vector &) = delete;
+    ref_small_vector &operator=(const ref_small_vector &) = delete;
+
+    // Move is allowed
+    ref_small_vector(ref_small_vector &&other) noexcept : m_ptrs(std::move(other.m_ptrs)) {
+        // other.m_ptrs is now empty, so its destructor won't decref anything
+    }
+
+    ref_small_vector &operator=(ref_small_vector &&other) noexcept {
+        if (this != &other) {
+            // Decref our current contents
+            for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
+                Py_XDECREF(m_ptrs[i]);
+            }
+            m_ptrs = std::move(other.m_ptrs);
+        }
+        return *this;
+    }
+
+    /// Add a pointer, taking ownership (no incref, will decref on destruction)
+    void push_back_steal(PyObject *p) { m_ptrs.push_back(p); }
+
+    /// Add a pointer, borrowing (increfs now, will decref on destruction)
+    void push_back_borrow(PyObject *p) {
+        Py_XINCREF(p);
+        m_ptrs.push_back(p);
+    }
+
+    /// Add a null pointer (for PY_VECTORCALL_ARGUMENTS_OFFSET slot)
+    void push_back_null() { m_ptrs.push_back(nullptr); }
+
+    void reserve(std::size_t sz) { m_ptrs.reserve(sz); }
+
+    std::size_t size() const { return m_ptrs.size(); }
+
+    PyObject *operator[](std::size_t idx) const { return m_ptrs[idx]; }
+
+    PyObject *const *data() const { return m_ptrs.data(); }
+
+private:
+    small_vector<PyObject *, InlineSize> m_ptrs;
+};
+
+PYBIND11_NAMESPACE_END(detail)
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/detail/class.h pybind11-3.0.4/include/pybind11/detail/class.h
--- pybind11-3.0.1/include/pybind11/detail/class.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/class.h	2026-04-19 06:01:51.000000000 +0300
@@ -71,7 +71,7 @@
        issue no Python C API calls which could potentially invoke the
        garbage collector (the GC will call type_traverse(), which will in
        turn find the newly constructed type in an invalid state) */
-    auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
+    auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
     if (!heap_type) {
         pybind11_fail("make_static_property_type(): error allocating type!");
     }
@@ -98,7 +98,7 @@
         pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
     }
 
-    setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
+    setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
     PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
 
     return type;
@@ -207,7 +207,7 @@
 
 /// Cleanup the type-info for a pybind11-registered type.
 extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
-    with_internals([obj](internals &internals) {
+    with_internals_if_internals([obj](internals &internals) {
         auto *type = (PyTypeObject *) obj;
 
         // A pybind11-registered type will:
@@ -221,10 +221,19 @@
             auto tindex = std::type_index(*tinfo->cpptype);
             internals.direct_conversions.erase(tindex);
 
+            auto &local_internals = get_local_internals();
             if (tinfo->module_local) {
-                get_local_internals().registered_types_cpp.erase(tindex);
+                local_internals.registered_types_cpp.erase(tinfo->cpptype);
             } else {
                 internals.registered_types_cpp.erase(tindex);
+#if PYBIND11_INTERNALS_VERSION >= 12
+                internals.registered_types_cpp_fast.erase(tinfo->cpptype);
+                for (const std::type_info *alias : tinfo->alias_chain) {
+                    auto num_erased = internals.registered_types_cpp_fast.erase(alias);
+                    (void) num_erased;
+                    assert(num_erased > 0);
+                }
+#endif
             }
             internals.registered_types_py.erase(tinfo->type);
 
@@ -256,7 +265,7 @@
        issue no Python C API calls which could potentially invoke the
        garbage collector (the GC will call type_traverse(), which will in
        turn find the newly constructed type in an invalid state) */
-    auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
+    auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
     if (!heap_type) {
         pybind11_fail("make_default_metaclass(): error allocating metaclass!");
     }
@@ -282,7 +291,7 @@
         pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
     }
 
-    setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
+    setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
     PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
 
     return type;
@@ -297,7 +306,7 @@
                                   instance *self,
                                   bool (*f)(void * /*parentptr*/, instance * /*self*/)) {
     for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
-        if (auto *parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
+        if (auto *parent_tinfo = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()))) {
             for (auto &c : parent_tinfo->implicit_casts) {
                 if (c.first == tinfo->cpptype) {
                     auto *parentptr = c.second(valueptr);
@@ -314,8 +323,9 @@
 
 #ifdef Py_GIL_DISABLED
 inline void enable_try_inc_ref(PyObject *obj) {
-    // TODO: Replace with PyUnstable_Object_EnableTryIncRef when available.
-    // See https://github.com/python/cpython/issues/128844
+#    if PY_VERSION_HEX >= 0x030E00A4
+    PyUnstable_EnableTryIncRef(obj);
+#    else
     if (_Py_IsImmortal(obj)) {
         return;
     }
@@ -330,10 +340,12 @@
             return;
         }
     }
+#    endif
 }
 #endif
 
 inline bool register_instance_impl(void *ptr, instance *self) {
+    assert(ptr);
 #ifdef Py_GIL_DISABLED
     enable_try_inc_ref(reinterpret_cast<PyObject *>(self));
 #endif
@@ -341,6 +353,7 @@
     return true; // unused, but gives the same signature as the deregister func
 }
 inline bool deregister_instance_impl(void *ptr, instance *self) {
+    assert(ptr);
     return with_instance_map(ptr, [&](instance_map &instances) {
         auto range = instances.equal_range(ptr);
         for (auto it = range.first; it != range.second; ++it) {
@@ -490,6 +503,17 @@
         PyObject_GC_UnTrack(self);
     }
 
+#if PY_VERSION_HEX >= 0x030D0000
+    // PyObject_ClearManagedDict() is available from Python 3.13+. It must be
+    // called before tp_free() because on Python 3.14+ tp_free no longer
+    // implicitly clears the managed dict, which would abandon the refcounts of
+    // objects stored in __dict__ of py::dynamic_attr() types, causing permanent
+    // memory leaks.
+    if (PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) {
+        PyObject_ClearManagedDict(self);
+    }
+#endif
+
     clear_instance(self);
 
     type->tp_free(self);
@@ -517,7 +541,7 @@
        issue no Python C API calls which could potentially invoke the
        garbage collector (the GC will call type_traverse(), which will in
        turn find the newly constructed type in an invalid state) */
-    auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
+    auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
     if (!heap_type) {
         pybind11_fail("make_object_base_type(): error allocating type!");
     }
@@ -544,17 +568,20 @@
         pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string());
     }
 
-    setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
+    setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
     PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
 
     assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
-    return (PyObject *) heap_type;
+    return reinterpret_cast<PyObject *>(heap_type);
 }
 
 /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
 extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
 #if PY_VERSION_HEX >= 0x030D0000
-    PyObject_VisitManagedDict(self, visit, arg);
+    int ret = PyObject_VisitManagedDict(self, visit, arg);
+    if (ret) {
+        return ret;
+    }
 #else
     PyObject *&dict = *_PyObject_GetDictPtr(self);
     Py_VISIT(dict);
@@ -733,7 +760,7 @@
         /* Allocate memory for docstring (Python will free this later on) */
         size_t size = std::strlen(rec.doc) + 1;
 #if PY_VERSION_HEX >= 0x030D0000
-        tp_doc = (char *) PyMem_MALLOC(size);
+        tp_doc = static_cast<char *>(PyMem_MALLOC(size));
 #else
         tp_doc = (char *) PyObject_MALLOC(size);
 #endif
@@ -748,10 +775,10 @@
        issue no Python C API calls which could potentially invoke the
        garbage collector (the GC will call type_traverse(), which will in
        turn find the newly constructed type in an invalid state) */
-    auto *metaclass
-        = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() : internals.default_metaclass;
+    auto *metaclass = rec.metaclass.ptr() ? reinterpret_cast<PyTypeObject *>(rec.metaclass.ptr())
+                                          : internals.default_metaclass;
 
-    auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
+    auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
     if (!heap_type) {
         pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
     }
@@ -764,7 +791,7 @@
     auto *type = &heap_type->ht_type;
     type->tp_name = full_name;
     type->tp_doc = tp_doc;
-    type->tp_base = type_incref((PyTypeObject *) base);
+    type->tp_base = type_incref(reinterpret_cast<PyTypeObject *>(base));
     type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
     if (!bases.empty()) {
         type->tp_bases = bases.release().ptr();
@@ -805,18 +832,18 @@
 
     /* Register type with the parent scope */
     if (rec.scope) {
-        setattr(rec.scope, rec.name, (PyObject *) type);
+        setattr(rec.scope, rec.name, reinterpret_cast<PyObject *>(type));
     } else {
         Py_INCREF(type); // Keep it alive forever (reference leak)
     }
 
     if (module_) { // Needed by pydoc
-        setattr((PyObject *) type, "__module__", module_);
+        setattr(reinterpret_cast<PyObject *>(type), "__module__", module_);
     }
 
     PYBIND11_SET_OLDPY_QUALNAME(type, qualname);
 
-    return (PyObject *) type;
+    return reinterpret_cast<PyObject *>(type);
 }
 
 PYBIND11_NAMESPACE_END(detail)
diff -Nru pybind11-3.0.1/include/pybind11/detail/common.h pybind11-3.0.4/include/pybind11/detail/common.h
--- pybind11-3.0.1/include/pybind11/detail/common.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/common.h	2026-04-19 06:01:51.000000000 +0300
@@ -19,7 +19,7 @@
 /* -- start version constants -- */
 #define PYBIND11_VERSION_MAJOR 3
 #define PYBIND11_VERSION_MINOR 0
-#define PYBIND11_VERSION_MICRO 1
+#define PYBIND11_VERSION_MICRO 4
 // ALPHA = 0xA, BETA = 0xB, GAMMA = 0xC (release candidate), FINAL = 0xF (stable release)
 // - The release level is set to "alpha" for development versions.
 //   Use 0xA0 (LEVEL=0xA, SERIAL=0) for development versions.
@@ -27,7 +27,7 @@
 #define PYBIND11_VERSION_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL
 #define PYBIND11_VERSION_RELEASE_SERIAL 0
 // String version of (micro, release level, release serial), e.g.: 0a0, 0b1, 0rc1, 0
-#define PYBIND11_VERSION_PATCH 1
+#define PYBIND11_VERSION_PATCH 4
 /* -- end version constants -- */
 
 #if !defined(Py_PACK_FULL_VERSION)
@@ -87,7 +87,7 @@
 #    endif
 #endif
 
-#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914))
+#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1920)) // See PR #5968
 #    define PYBIND11_STD_LAUNDER std::launder
 #    define PYBIND11_HAS_STD_LAUNDER 1
 #else
@@ -103,6 +103,10 @@
 #    define PYBIND11_DTOR_CONSTEXPR
 #endif
 
+#if defined(PYBIND11_CPP20) && defined(__has_include) && __has_include(<barrier>)
+#    define PYBIND11_HAS_STD_BARRIER 1
+#endif
+
 // Compiler version assertions
 #if defined(__INTEL_COMPILER)
 #    if __INTEL_COMPILER < 1800
@@ -163,6 +167,14 @@
 #    define PYBIND11_NOINLINE __attribute__((noinline)) inline
 #endif
 
+#if defined(_MSC_VER)
+#    define PYBIND11_ALWAYS_INLINE __forceinline
+#elif defined(__GNUC__)
+#    define PYBIND11_ALWAYS_INLINE __attribute__((__always_inline__)) inline
+#else
+#    define PYBIND11_ALWAYS_INLINE inline
+#endif
+
 #if defined(__MINGW32__)
 // For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared
 // whether it is used or not
@@ -248,6 +260,10 @@
 #    define PYBIND11_HAS_U8STRING 1
 #endif
 
+#if defined(PYBIND11_CPP20) && defined(__cpp_lib_span) && __cpp_lib_span >= 202002L
+#    define PYBIND11_HAS_SPAN 1
+#endif
+
 // See description of PR #4246:
 #if !defined(PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF) && !defined(NDEBUG)                       \
     && !defined(PYPY_VERSION) && !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
@@ -322,6 +338,13 @@
 #define PYBIND11_BYTES_AS_STRING PyBytes_AsString
 #define PYBIND11_BYTES_SIZE PyBytes_Size
 #define PYBIND11_LONG_CHECK(o) PyLong_Check(o)
+// In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`,
+// while CPython only considers the existence of `nb_index`/`__index__`.
+#if !defined(PYPY_VERSION)
+#    define PYBIND11_INDEX_CHECK(o) PyIndex_Check(o)
+#else
+#    define PYBIND11_INDEX_CHECK(o) hasattr(o, "__index__")
+#endif
 #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o)
 #define PYBIND11_LONG_FROM_SIGNED(o) PyLong_FromSsize_t((ssize_t) (o))
 #define PYBIND11_LONG_FROM_UNSIGNED(o) PyLong_FromSize_t((size_t) (o))
@@ -430,30 +453,35 @@
 possibly concurrently.  The PyModuleDef is allowed to be static, but the PyObject* resulting from
 PyModuleDef_Init should be treated like any other PyObject (so not shared across interpreters).
  */
-#define PYBIND11_MODULE_PYINIT(name, pre_init, ...)                                               \
+#define PYBIND11_MODULE_PYINIT(name, ...)                                                         \
     static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *);                                 \
     PYBIND11_PLUGIN_IMPL(name) {                                                                  \
         PYBIND11_CHECK_PYTHON_VERSION                                                             \
-        pre_init;                                                                                 \
-        PYBIND11_ENSURE_INTERNALS_READY                                                           \
-        static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots(    \
-            &PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__);                               \
-        static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT,                                \
-                               /* m_name */ PYBIND11_TOSTRING(name),                              \
-                               /* m_doc */ nullptr,                                               \
-                               /* m_size */ 0,                                                    \
-                               /* m_methods */ nullptr,                                           \
-                               /* m_slots */ mod_def_slots.data(),                                \
-                               /* m_traverse */ nullptr,                                          \
-                               /* m_clear */ nullptr,                                             \
-                               /* m_free */ nullptr};                                             \
-        return PyModuleDef_Init(&def);                                                            \
+        try {                                                                                     \
+            pybind11::detail::ensure_internals();                                                 \
+            static ::pybind11::detail::slots_array mod_def_slots                                  \
+                = ::pybind11::detail::init_slots(&PYBIND11_CONCAT(pybind11_exec_, name),          \
+                                                 ##__VA_ARGS__);                                  \
+            static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT,                            \
+                                   /* m_name */ PYBIND11_TOSTRING(name),                          \
+                                   /* m_doc */ nullptr,                                           \
+                                   /* m_size */ 0,                                                \
+                                   /* m_methods */ nullptr,                                       \
+                                   /* m_slots */ mod_def_slots.data(),                            \
+                                   /* m_traverse */ nullptr,                                      \
+                                   /* m_clear */ nullptr,                                         \
+                                   /* m_free */ nullptr};                                         \
+            return PyModuleDef_Init(&def);                                                        \
+        }                                                                                         \
+        PYBIND11_CATCH_INIT_EXCEPTIONS                                                            \
+        return nullptr;                                                                           \
     }
 
 #define PYBIND11_MODULE_EXEC(name, variable)                                                      \
     static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &);                     \
     int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) {                                    \
         try {                                                                                     \
+            pybind11::detail::ensure_internals();                                                 \
             auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm);                       \
             if (!pybind11::detail::get_cached_module(m.attr("__spec__").attr("name"))) {          \
                 PYBIND11_CONCAT(pybind11_init_, name)(m);                                         \
@@ -507,8 +535,7 @@
 
 \endrst */
 #define PYBIND11_MODULE(name, variable, ...)                                                      \
-    PYBIND11_MODULE_PYINIT(                                                                       \
-        name, (pybind11::detail::get_num_interpreters_seen() += 1), ##__VA_ARGS__)                \
+    PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__)                                                   \
     PYBIND11_MODULE_EXEC(name, variable)
 
 // pop gnu-zero-variadic-macro-arguments
@@ -579,14 +606,10 @@
 
 PYBIND11_NAMESPACE_BEGIN(detail)
 
-inline static constexpr int log2(size_t n, int k = 0) {
-    return (n <= 1) ? k : log2(n >> 1, k + 1);
-}
+static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); }
 
 // Returns the size as a multiple of sizeof(void *), rounded up.
-inline static constexpr size_t size_in_ptrs(size_t s) {
-    return 1 + ((s - 1) >> log2(sizeof(void *)));
-}
+static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); }
 
 /**
  * The space to allocate for simple layout instance holders (see below) in multiple of the size of
@@ -1016,6 +1039,17 @@
 template <typename T>
 using is_shared_ptr = is_instantiation<std::shared_ptr, T>;
 
+/// Detects whether static_cast<Derived*>(Base*) is valid, i.e. the inheritance is non-virtual.
+/// Used to detect virtual bases: if this is false, pointer adjustments require the implicit_casts
+/// chain rather than reinterpret_cast.
+template <typename Base, typename Derived, typename = void>
+struct is_static_downcastable : std::false_type {};
+template <typename Base, typename Derived>
+struct is_static_downcastable<Base,
+                              Derived,
+                              void_t<decltype(static_cast<Derived *>(std::declval<Base *>()))>>
+    : std::true_type {};
+
 /// Check if T looks like an input iterator
 template <typename T, typename = void>
 struct is_input_iterator : std::false_type {};
@@ -1038,14 +1072,30 @@
     using type = typename remove_class<decltype(&F::operator())>::type;
 };
 
+// Strip noexcept from a free function type (C++17: noexcept is part of the type).
+template <typename T>
+struct remove_noexcept {
+    using type = T;
+};
+#ifdef __cpp_noexcept_function_type
+template <typename R, typename... A>
+struct remove_noexcept<R(A...) noexcept> {
+    using type = R(A...);
+};
+#endif
+template <typename T>
+using remove_noexcept_t = typename remove_noexcept<T>::type;
+
 // Extracts the function signature from a function, function pointer or lambda.
+// Strips noexcept from the result so that factory/pickle_factory partial specializations
+// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
 template <typename Function, typename F = remove_reference_t<Function>>
-using function_signature_t = conditional_t<
+using function_signature_t = remove_noexcept_t<conditional_t<
     std::is_function<F>::value,
     F,
     typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
                            std::remove_pointer<F>,
-                           strip_function_object<F>>::type>;
+                           strip_function_object<F>>::type>>;
 
 /// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
 /// pointer.  Note that this can catch all sorts of other things, too; this is intended to be used
@@ -1194,6 +1244,36 @@
         -> decltype(pmf) {
         return pmf;
     }
+
+    // Define const/non-const member-pointer selector pairs for qualifier combinations.
+    // The `qualifiers` parameter is used in type position, where extra parentheses are invalid.
+    // NOLINTBEGIN(bugprone-macro-parentheses)
+#define PYBIND11_OVERLOAD_CAST_MEMBER_PTR(qualifiers)                                             \
+    template <typename Return, typename Class>                                                    \
+    constexpr auto operator()(Return (Class::*pmf)(Args...) qualifiers, std::false_type = {})     \
+        const noexcept -> decltype(pmf) {                                                         \
+        return pmf;                                                                               \
+    }                                                                                             \
+    template <typename Return, typename Class>                                                    \
+    constexpr auto operator()(Return (Class::*pmf)(Args...) const qualifiers, std::true_type)     \
+        const noexcept -> decltype(pmf) {                                                         \
+        return pmf;                                                                               \
+    }
+    PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&)
+    PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&&)
+
+#ifdef __cpp_noexcept_function_type
+    template <typename Return>
+    constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
+        return pf;
+    }
+
+    PYBIND11_OVERLOAD_CAST_MEMBER_PTR(noexcept)
+    PYBIND11_OVERLOAD_CAST_MEMBER_PTR(& noexcept)
+    PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&& noexcept)
+#endif
+#undef PYBIND11_OVERLOAD_CAST_MEMBER_PTR
+    // NOLINTEND(bugprone-macro-parentheses)
 };
 PYBIND11_NAMESPACE_END(detail)
 
@@ -1293,8 +1373,7 @@
 #if defined(_MSC_VER) && _MSC_VER < 1920 // MSVC 2017
 constexpr
 #endif
-    inline void
-    silence_unused_warnings(Args &&...) {
+    inline void silence_unused_warnings(Args &&...) {
 }
 
 // MSVC warning C4100: Unreferenced formal parameter
diff -Nru pybind11-3.0.1/include/pybind11/detail/cpp_conduit.h pybind11-3.0.4/include/pybind11/detail/cpp_conduit.h
--- pybind11-3.0.1/include/pybind11/detail/cpp_conduit.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/cpp_conduit.h	2026-04-19 06:01:51.000000000 +0300
@@ -21,13 +21,13 @@
     return bool(internals.registered_types_py.find(type_obj)
                 != internals.registered_types_py.end());
 #else
-    return bool(type_obj->tp_new == pybind11_object_new);
+    return (type_obj->tp_new == pybind11_object_new);
 #endif
 }
 
 inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) {
     PyObject *descr = _PyType_Lookup(type_obj, attr_name);
-    return bool((descr != nullptr) && PyInstanceMethod_Check(descr));
+    return ((descr != nullptr) && PyInstanceMethod_Check(descr));
 }
 
 inline object try_get_cpp_conduit_method(PyObject *obj) {
diff -Nru pybind11-3.0.1/include/pybind11/detail/function_record_pyobject.h pybind11-3.0.4/include/pybind11/detail/function_record_pyobject.h
--- pybind11-3.0.1/include/pybind11/detail/function_record_pyobject.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/function_record_pyobject.h	2026-04-19 06:01:51.000000000 +0300
@@ -91,6 +91,7 @@
        function_record_PyType_Slots};
 
 inline PyTypeObject *get_function_record_PyTypeObject() {
+    PYBIND11_LOCK_INTERNALS(get_internals());
     PyTypeObject *&py_type_obj = detail::get_local_internals().function_record_py_type;
     if (!py_type_obj) {
         PyObject *py_obj = PyType_FromSpec(&function_record_PyType_Spec);
@@ -125,7 +126,7 @@
 
 inline function_record *function_record_ptr_from_PyObject(PyObject *obj) {
     if (is_function_record_PyObject(obj)) {
-        return ((detail::function_record_PyObject *) obj)->cpp_func_rec;
+        return (reinterpret_cast<detail::function_record_PyObject *>(obj))->cpp_func_rec;
     }
     return nullptr;
 }
@@ -136,7 +137,7 @@
         throw error_already_set();
     }
     py_func_rec->cpp_func_rec = nullptr; // For clarity/purity. Redundant in practice.
-    return reinterpret_steal<object>((PyObject *) py_func_rec);
+    return reinterpret_steal<object>(reinterpret_cast<PyObject *>(py_func_rec));
 }
 
 PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
diff -Nru pybind11-3.0.1/include/pybind11/detail/holder_caster_foreign_helpers.h pybind11-3.0.4/include/pybind11/detail/holder_caster_foreign_helpers.h
--- pybind11-3.0.1/include/pybind11/detail/holder_caster_foreign_helpers.h	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/include/pybind11/detail/holder_caster_foreign_helpers.h	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,104 @@
+/*
+    pybind11/detail/holder_caster_foreign_helpers.h: Logic to implement
+    set_foreign_holder() in copyable_ and movable_holder_caster.
+
+    Copyright (c) 2025 Hudson River Trading LLC <opensource@hudson-trading.com>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include <pybind11/gil.h>
+
+#include "common.h"
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+struct holder_caster_foreign_helpers {
+    struct py_deleter {
+        void operator()(const void *) const noexcept {
+            // Don't run the deleter if the interpreter has been shut down
+            if (Py_IsInitialized() == 0) {
+                return;
+            }
+            gil_scoped_acquire guard;
+            Py_DECREF(o);
+        }
+
+        PyObject *o;
+    };
+
+    // Downcast shared_ptr from the enable_shared_from_this base to the target type.
+    // SFINAE probe: use static_pointer_cast when the static downcast is valid (common case),
+    // fall back to dynamic_pointer_cast when it isn't (virtual inheritance — issue #5989).
+    // We can't use dynamic_pointer_cast unconditionally because it requires polymorphic types;
+    // we can't use is_polymorphic to choose because that's orthogonal to virtual inheritance.
+    // (The implementation uses the "tag dispatch via overload priority" trick.)
+    template <typename type, typename esft_base>
+    static auto esft_downcast(const std::shared_ptr<esft_base> &existing, int /*preferred*/)
+        -> decltype(static_cast<type *>(std::declval<esft_base *>()), std::shared_ptr<type>()) {
+        return std::static_pointer_cast<type>(existing);
+    }
+
+    template <typename type, typename esft_base>
+    static std::shared_ptr<type> esft_downcast(const std::shared_ptr<esft_base> &existing,
+                                               ... /*fallback*/) {
+        return std::dynamic_pointer_cast<type>(existing);
+    }
+
+    template <typename type>
+    static auto set_via_shared_from_this(type *value, std::shared_ptr<type> *holder_out)
+        -> decltype(value->shared_from_this(), bool()) {
+        // object derives from enable_shared_from_this;
+        // try to reuse an existing shared_ptr if one is known
+        if (auto existing = try_get_shared_from_this(value)) {
+            *holder_out = esft_downcast<type>(existing, 0);
+            return true;
+        }
+        return false;
+    }
+
+    template <typename type>
+    static bool set_via_shared_from_this(void *, std::shared_ptr<type> *) {
+        return false;
+    }
+
+    template <typename type>
+    static bool set_foreign_holder(handle src, type *value, std::shared_ptr<type> *holder_out) {
+        // We only support using std::shared_ptr<T> for foreign T, and
+        // it's done by creating a new shared_ptr control block that
+        // owns a reference to the original Python object.
+        if (value == nullptr) {
+            *holder_out = {};
+            return true;
+        }
+        if (set_via_shared_from_this(value, holder_out)) {
+            return true;
+        }
+        *holder_out = std::shared_ptr<type>(value, py_deleter{src.inc_ref().ptr()});
+        return true;
+    }
+
+    template <typename type>
+    static bool
+    set_foreign_holder(handle src, const type *value, std::shared_ptr<const type> *holder_out) {
+        std::shared_ptr<type> holder_mut;
+        if (set_foreign_holder(src, const_cast<type *>(value), &holder_mut)) {
+            *holder_out = holder_mut;
+            return true;
+        }
+        return false;
+    }
+
+    template <typename type>
+    static bool set_foreign_holder(handle, type *, ...) {
+        throw cast_error("Unable to cast foreign type to held instance -- "
+                         "only std::shared_ptr<T> is supported in this case");
+    }
+};
+
+PYBIND11_NAMESPACE_END(detail)
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/detail/init.h pybind11-3.0.4/include/pybind11/detail/init.h
--- pybind11-3.0.1/include/pybind11/detail/init.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/init.h	2026-04-19 06:01:51.000000000 +0300
@@ -304,11 +304,10 @@
             extra...);
     }
 
-    template <
-        typename Class,
-        typename... Extra,
-        enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, int>
-        = 0>
+    template <typename Class,
+              typename... Extra,
+              enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value,
+                          int> = 0>
     static void execute(Class &cl, const Extra &...extra) {
         cl.def(
             "__init__",
@@ -325,11 +324,10 @@
             extra...);
     }
 
-    template <
-        typename Class,
-        typename... Extra,
-        enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, int>
-        = 0>
+    template <typename Class,
+              typename... Extra,
+              enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value,
+                          int> = 0>
     static void execute(Class &cl, const Extra &...extra) {
         cl.def(
             "__init__",
@@ -345,11 +343,10 @@
 // Implementing class for py::init_alias<...>()
 template <typename... Args>
 struct alias_constructor {
-    template <
-        typename Class,
-        typename... Extra,
-        enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int>
-        = 0>
+    template <typename Class,
+              typename... Extra,
+              enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value,
+                          int> = 0>
     static void execute(Class &cl, const Extra &...extra) {
         cl.def(
             "__init__",
@@ -370,8 +367,15 @@
 struct factory;
 
 // Specialization for py::init(Func)
+// Note: The 4th template parameter `void_type()` is explicitly specified to resolve a
+// template ambiguity with the dual-factory specialization below when compiled with
+// nvcc + GCC (see #5565). Without it, both specializations match equally well for the
+// single-factory case, since the 4th parameter defaults to
+// `function_signature_t<void_type(*)()>` = `void_type()`, which the dual-factory
+// specialization can also decompose as `AReturn(AArgs...)` with `AReturn=void_type`
+// and `AArgs={}`.
 template <typename Func, typename Return, typename... Args>
-struct factory<Func, void_type (*)(), Return(Args...)> {
+struct factory<Func, void_type (*)(), Return(Args...), void_type()> {
     remove_reference_t<Func> class_factory;
 
     // NOLINTNEXTLINE(google-explicit-constructor)
@@ -476,9 +480,9 @@
         return;
     }
     // Our tests never run into an unset dict, but being careful here for now (see #5658)
-    auto dict = getattr((PyObject *) v_h.inst, "__dict__", none());
+    auto dict = getattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", none());
     if (dict.is_none()) {
-        setattr((PyObject *) v_h.inst, "__dict__", d);
+        setattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", d);
     } else {
         // Keep the original object dict and just update it
         if (PyDict_Update(dict.ptr(), d.ptr()) < 0) {
@@ -501,9 +505,15 @@
           typename NewInstance,
           typename ArgState>
 struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
-    static_assert(std::is_same<intrinsic_t<RetState>, intrinsic_t<ArgState>>::value,
-                  "The type returned by `__getstate__` must be the same "
-                  "as the argument accepted by `__setstate__`");
+    using Ret = intrinsic_t<RetState>;
+    using Arg = intrinsic_t<ArgState>;
+
+    // Subclasses are now allowed for support between type hint and generic versions of types
+    // (e.g.) typing::List <--> list
+    static_assert(std::is_same<Ret, Arg>::value || std::is_base_of<Ret, Arg>::value
+                      || std::is_base_of<Arg, Ret>::value,
+                  "The type returned by `__getstate__` must be the same or subclass of the "
+                  "argument accepted by `__setstate__`");
 
     remove_reference_t<Get> get;
     remove_reference_t<Set> set;
diff -Nru pybind11-3.0.1/include/pybind11/detail/internals.h pybind11-3.0.4/include/pybind11/detail/internals.h
--- pybind11-3.0.1/include/pybind11/detail/internals.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/internals.h	2026-04-19 06:01:51.000000000 +0300
@@ -59,15 +59,13 @@
 #    define PYBIND11_TLS_KEY_INIT(var)                                                            \
         _Pragma("clang diagnostic push")                                         /**/             \
             _Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/             \
-            Py_tss_t var                                                                          \
-            = Py_tss_NEEDS_INIT;                                                                  \
+            Py_tss_t var = Py_tss_NEEDS_INIT;                                                     \
         _Pragma("clang diagnostic pop")
 #elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
 #    define PYBIND11_TLS_KEY_INIT(var)                                                            \
         _Pragma("GCC diagnostic push")                                         /**/               \
             _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/               \
-            Py_tss_t var                                                                          \
-            = Py_tss_NEEDS_INIT;                                                                  \
+            Py_tss_t var = Py_tss_NEEDS_INIT;                                                     \
         _Pragma("GCC diagnostic pop")
 #else
 #    define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
@@ -103,7 +101,7 @@
         // However, in GraalPy (as of v24.2 or older), TSS is implemented by Java and this call
         // requires a living Python interpreter.
 #ifdef GRAALVM_PYTHON
-        if (!Py_IsInitialized() || _Py_IsFinalizing()) {
+        if (Py_IsInitialized() == 0 || _Py_IsFinalizing() != 0) {
             return;
         }
 #endif
@@ -143,6 +141,38 @@
 inline PyObject *make_object_base_type(PyTypeObject *metaclass);
 inline void translate_exception(std::exception_ptr p);
 
+inline PyThreadState *get_thread_state_unchecked() {
+#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
+    return PyThreadState_GET();
+#elif PY_VERSION_HEX < 0x030D0000
+    return _PyThreadState_UncheckedGet();
+#else
+    return PyThreadState_GetUnchecked();
+#endif
+}
+
+inline PyInterpreterState *get_interpreter_state_unchecked() {
+    auto *tstate = get_thread_state_unchecked();
+    return tstate ? tstate->interp : nullptr;
+}
+
+inline object get_python_state_dict() {
+    object state_dict;
+#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
+    state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
+#else
+    auto *istate = get_interpreter_state_unchecked();
+    if (istate) {
+        state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
+    }
+#endif
+    if (!state_dict) {
+        raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
+        throw error_already_set();
+    }
+    return state_dict;
+}
+
 // Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly
 // other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module
 // even when `A` is the same, non-hidden-visibility type (e.g. from a common include).  Under
@@ -176,11 +206,17 @@
 };
 #endif
 
+// For now, we don't bother adding a fancy hash for pointers and just
+// let the standard library use the identity hash function if that's
+// what it wants to do (e.g., as in libstdc++).
+template <typename value_type>
+using fast_type_map = std::unordered_map<const std::type_info *, value_type>;
+
 template <typename value_type>
 using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;
 
 struct override_hash {
-    inline size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
+    size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
         size_t value = std::hash<const void *>()(v.first);
         value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
         return value;
@@ -192,6 +228,7 @@
 #ifdef Py_GIL_DISABLED
 // Wrapper around PyMutex to provide BasicLockable semantics
 class pymutex {
+    friend class pycritical_section;
     PyMutex mutex;
 
 public:
@@ -200,6 +237,37 @@
     void unlock() { PyMutex_Unlock(&mutex); }
 };
 
+class pycritical_section {
+    pymutex &mutex;
+#    if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
+    PyCriticalSection cs;
+#    endif
+
+public:
+    explicit pycritical_section(pymutex &m) : mutex(m) {
+        // PyCriticalSection_BeginMutex was added in Python 3.15.0a1 and backported to 3.14.0rc1
+#    if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
+        PyCriticalSection_BeginMutex(&cs, &mutex.mutex);
+#    else
+        // Fall back to direct mutex locking for older free-threaded Python versions
+        mutex.lock();
+#    endif
+    }
+    ~pycritical_section() {
+#    if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
+        PyCriticalSection_End(&cs);
+#    else
+        mutex.unlock();
+#    endif
+    }
+
+    // Non-copyable and non-movable to prevent double-unlock
+    pycritical_section(const pycritical_section &) = delete;
+    pycritical_section &operator=(const pycritical_section &) = delete;
+    pycritical_section(pycritical_section &&) = delete;
+    pycritical_section &operator=(pycritical_section &&) = delete;
+};
+
 // Instance map shards are used to reduce mutex contention in free-threaded Python.
 struct instance_map_shard {
     instance_map registered_instances;
@@ -236,6 +304,15 @@
     pymutex mutex;
     pymutex exception_translator_mutex;
 #endif
+#if PYBIND11_INTERNALS_VERSION >= 12
+    // non-normative but fast "hint" for registered_types_cpp. Meant
+    // to be used as the first level of a two-level lookup: successful
+    // lookups are correct, but unsuccessful lookups need to try
+    // registered_types_cpp and then backfill this map if they find
+    // anything.
+    fast_type_map<type_info *> registered_types_cpp_fast;
+#endif
+
     // std::type_index -> pybind11's type information
     type_map<type_info *> registered_types_cpp;
     // PyTypeObject* -> base type_info(s)
@@ -260,7 +337,9 @@
     PyObject *instance_base = nullptr;
     // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
     thread_specific_storage<PyThreadState> tstate;
-    thread_specific_storage<loader_life_support> loader_life_support_tls;
+#if PYBIND11_INTERNALS_VERSION <= 11
+    thread_specific_storage<loader_life_support> loader_life_support_tls; // OBSOLETE (PR #5830)
+#endif
     // Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
     PyInterpreterState *istate = nullptr;
 
@@ -268,11 +347,8 @@
 
     internals()
         : static_property_type(make_static_property_type()),
-          default_metaclass(make_default_metaclass()) {
-        PyThreadState *cur_tstate = PyThreadState_Get();
-        tstate = cur_tstate;
-
-        istate = cur_tstate->interp;
+          default_metaclass(make_default_metaclass()), istate(get_interpreter_state_unchecked()) {
+        tstate.set(nullptr); // See PR #5870
         registered_exception_translators.push_front(&translate_exception);
 #ifdef Py_GIL_DISABLED
         // Scale proportional to the number of cores. 2x is a heuristic to reduce contention.
@@ -301,7 +377,11 @@
 // impact any other modules, because the only things accessing the local internals is the
 // module that contains them.
 struct local_internals {
-    type_map<type_info *> registered_types_cpp;
+    // It should be safe to use fast_type_map here because this entire
+    // data structure is scoped to our single module, and thus a single
+    // DSO and single instance of type_info for any particular type.
+    fast_type_map<type_info *> registered_types_cpp;
+
     std::forward_list<ExceptionTranslator> registered_exception_translators;
     PyTypeObject *function_record_py_type = nullptr;
 };
@@ -336,6 +416,20 @@
     void *get_buffer_data = nullptr;
     void *(*module_local_load)(PyObject *, const type_info *) = nullptr;
     holder_enum_t holder_enum_v = holder_enum_t::undefined;
+
+#if PYBIND11_INTERNALS_VERSION >= 12
+    // When a type appears in multiple DSOs,
+    // internals::registered_types_cpp_fast will have multiple distinct
+    // keys (the std::type_info from each DSO) mapped to the same
+    // detail::type_info*. We need to keep track of these aliases so that we clean
+    // them up when our type is deallocated. A linked list is appropriate
+    // because it is expected to be 1) usually empty and 2)
+    // when it's not empty, usually very small. See also `struct
+    // nb_alias_chain` added in
+    // https://github.com/wjakob/nanobind/commit/b515b1f7f2f4ecc0357818e6201c94a9f4cbfdc2
+    std::forward_list<const std::type_info *> alias_chain;
+#endif
+
     /* A simple type never occurs as a (direct or indirect) parent
      * of a class that makes use of multiple inheritance.
      * A type can be simple even if it has non-simple ancestors as long as it has no descendants.
@@ -347,6 +441,24 @@
     bool module_local : 1;
 };
 
+/// Information stored in a capsule on py::native_enum() types. Since we don't
+/// create a type_info record for native enums, we must store here any
+/// information we will need about the enum at runtime.
+///
+/// If you make backward-incompatible changes to this structure, you must
+/// change the `attribute_name()` so that native enums from older version of
+/// pybind11 don't have their records reinterpreted. Better would be to keep
+/// the changes backward-compatible (i.e., only add new fields at the end)
+/// and detect/indicate their presence using the currently-unused `version`.
+struct native_enum_record {
+    const std::type_info *cpptype;
+    uint32_t size_bytes;
+    bool is_signed;
+    const uint8_t version = 1;
+
+    static const char *attribute_name() { return "__pybind11_native_enum__"; }
+};
+
 #define PYBIND11_INTERNALS_ID                                                                     \
     "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION)                        \
         PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
@@ -355,21 +467,12 @@
     "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION)                     \
         PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
 
-inline PyThreadState *get_thread_state_unchecked() {
-#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
-    return PyThreadState_GET();
-#elif PY_VERSION_HEX < 0x030D0000
-    return _PyThreadState_UncheckedGet();
-#else
-    return PyThreadState_GetUnchecked();
-#endif
-}
-
-/// We use this counter to figure out if there are or have been multiple subinterpreters active at
-/// any point. This must never decrease while any interpreter may be running in any thread!
-inline std::atomic<int> &get_num_interpreters_seen() {
-    static std::atomic<int> counter(0);
-    return counter;
+/// We use this to figure out if there are or have been multiple subinterpreters active at any
+/// point. This must never go from true to false while any interpreter may be running in any
+/// thread!
+inline std::atomic_bool &has_seen_non_main_interpreter() {
+    static std::atomic_bool multi(false);
+    return multi;
 }
 
 template <class T,
@@ -476,52 +579,126 @@
 }
 #endif
 
-inline object get_python_state_dict() {
-    object state_dict;
-#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
-    state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
-#else
-#    if PY_VERSION_HEX < 0x03090000
-    PyInterpreterState *istate = _PyInterpreterState_Get();
-#    else
-    PyInterpreterState *istate = PyInterpreterState_Get();
-#    endif
-    if (istate) {
-        state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
+// Sentinel value for the `dtor` parameter of `atomic_get_or_create_in_state_dict`.
+// Indicates no destructor was explicitly provided (distinct from nullptr, which means "leak").
+#define PYBIND11_DTOR_USE_DELETE (reinterpret_cast<void (*)(PyObject *)>(1))
+
+// Get or create per-storage capsule in the current interpreter's state dict.
+//   - The storage is interpreter-dependent: different interpreters will have different storage.
+//     This is important when using multiple-interpreters, to avoid sharing unshareable objects
+//     between interpreters.
+//   - There is one storage per `key` in an interpreter and it is accessible between all extensions
+//     in the same interpreter.
+//   - The life span of the storage is tied to the interpreter: it will be kept alive until the
+//     interpreter shuts down.
+//
+// Use test-and-set pattern with `PyDict_SetDefault` for thread-safe concurrent access.
+// WARNING: There can be multiple threads creating the storage at the same time, while only one
+//          will succeed in inserting its capsule into the dict. Therefore, the deleter will be
+//          used to clean up the storage of the unused capsules.
+//
+// Returns: pair of (pointer to storage, bool indicating if newly created).
+//          The bool follows std::map::insert convention: true = created, false = existed.
+// `dtor`: optional destructor called when the interpreter shuts down.
+//   - If not provided: the storage will be deleted using `delete`.
+//   - If nullptr: the storage will be leaked (useful for singletons that outlive the interpreter).
+//   - If a function: that function will be called with the capsule object.
+template <typename Payload>
+std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
+                                                              void (*dtor)(PyObject *)
+                                                              = PYBIND11_DTOR_USE_DELETE) {
+    error_scope err_scope; // preserve any existing Python error states
+
+    auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
+    PyObject *capsule_obj = nullptr;
+    bool created = false;
+
+    // Try to get existing storage (fast path).
+    capsule_obj = dict_getitemstring(state_dict.ptr(), key);
+    if (capsule_obj == nullptr) {
+        if (PyErr_Occurred()) {
+            throw error_already_set();
+        }
+        // Storage doesn't exist yet, create a new one.
+        // Use unique_ptr for exception safety: if capsule creation throws, the storage is
+        // automatically deleted.
+        auto storage_ptr = std::unique_ptr<Payload>(new Payload{});
+        auto new_capsule
+            = capsule(storage_ptr.get(),
+                      // The destructor will be called when the capsule is GC'ed.
+                      //  If the insert below fails (entry already in the dict), then this
+                      //  destructor will be called on the newly created capsule at the end of this
+                      //  function, and we want to just release this memory.
+                      /*destructor=*/[](void *v) { delete static_cast<Payload *>(v); });
+        // At this point, the capsule object is created successfully.
+        // Release the unique_ptr and let the capsule object own the storage to avoid double-free.
+        (void) storage_ptr.release();
+
+        // Use `PyDict_SetDefault` for atomic test-and-set:
+        //   - If key doesn't exist, inserts our capsule and returns it.
+        //   - If key exists (another thread inserted first), returns the existing value.
+        // This is thread-safe because `PyDict_SetDefault` will hold a lock on the dict.
+        //
+        // NOTE: Here we use `PyDict_SetDefault` instead of `PyDict_SetDefaultRef` because the
+        //       capsule is kept alive until interpreter shutdown, so we do not need to handle
+        //       incref and decref here.
+        capsule_obj = dict_setdefaultstring(state_dict.ptr(), key, new_capsule.ptr());
+        if (capsule_obj == nullptr) {
+            throw error_already_set();
+        }
+        created = (capsule_obj == new_capsule.ptr());
+        // - If key already existed, our `new_capsule` is not inserted, it will be destructed when
+        //   going out of scope here, and will call the destructor set above.
+        // - Otherwise, our `new_capsule` is now in the dict, and it owns the storage and the state
+        //   dict will incref it.  We need to set the caller's destructor on it, which will be
+        //   called when the interpreter shuts down.
+        if (created && dtor != PYBIND11_DTOR_USE_DELETE) {
+            if (PyCapsule_SetDestructor(capsule_obj, dtor) < 0) {
+                throw error_already_set();
+            }
+        }
     }
-#endif
-    if (!state_dict) {
-        raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
+
+    // Get the storage pointer from the capsule.
+    void *raw_ptr = PyCapsule_GetPointer(capsule_obj, /*name=*/nullptr);
+    if (!raw_ptr) {
+        raise_from(PyExc_SystemError,
+                   "pybind11::detail::atomic_get_or_create_in_state_dict() FAILED");
         throw error_already_set();
     }
-    return state_dict;
+    return std::pair<Payload *, bool>(static_cast<Payload *>(raw_ptr), created);
 }
 
+#undef PYBIND11_DTOR_USE_DELETE
+
 template <typename InternalsType>
 class internals_pp_manager {
 public:
     using on_fetch_function = void(InternalsType *);
-    internals_pp_manager(char const *id, on_fetch_function *on_fetch)
-        : holder_id_(id), on_fetch_(on_fetch) {}
+
+    static internals_pp_manager &get_instance(char const *id, on_fetch_function *on_fetch) {
+        static internals_pp_manager instance(id, on_fetch);
+        return instance;
+    }
 
     /// Get the current pointer-to-pointer, allocating it if it does not already exist.  May
     /// acquire the GIL. Will never return nullptr.
     std::unique_ptr<InternalsType> *get_pp() {
 #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
-        if (get_num_interpreters_seen() > 1) {
+        if (has_seen_non_main_interpreter()) {
             // Whenever the interpreter changes on the current thread we need to invalidate the
             // internals_pp so that it can be pulled from the interpreter's state dict.  That is
             // slow, so we use the current PyThreadState to check if it is necessary.
             auto *tstate = get_thread_state_unchecked();
-            if (!tstate || tstate->interp != last_istate_.get()) {
+            if (!tstate || tstate->interp != last_istate_tls()) {
                 gil_scoped_acquire_simple gil;
                 if (!tstate) {
                     tstate = get_thread_state_unchecked();
                 }
-                last_istate_ = tstate->interp;
-                internals_tls_p_ = get_or_create_pp_in_state_dict();
+                last_istate_tls() = tstate->interp;
+                internals_p_tls() = get_or_create_pp_in_state_dict();
             }
-            return internals_tls_p_.get();
+            return internals_p_tls();
         }
 #endif
         if (!internals_singleton_pp_) {
@@ -534,9 +711,9 @@
     /// Drop all the references we're currently holding.
     void unref() {
 #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
-        if (get_num_interpreters_seen() > 1) {
-            last_istate_.reset();
-            internals_tls_p_.reset();
+        if (has_seen_non_main_interpreter()) {
+            last_istate_tls() = nullptr;
+            internals_p_tls() = nullptr;
             return;
         }
 #endif
@@ -545,56 +722,111 @@
 
     void destroy() {
 #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
-        if (get_num_interpreters_seen() > 1) {
+        if (has_seen_non_main_interpreter()) {
             auto *tstate = get_thread_state_unchecked();
             // this could be called without an active interpreter, just use what was cached
-            if (!tstate || tstate->interp == last_istate_.get()) {
-                auto tpp = internals_tls_p_.get();
-                if (tpp) {
-                    delete tpp;
+            if (!tstate || tstate->interp == last_istate_tls()) {
+                auto tpp = internals_p_tls();
+                {
+                    std::lock_guard<std::mutex> lock(pp_set_mutex_);
+                    pps_have_created_content_.erase(tpp); // untrack deleted pp
                 }
+                delete tpp; // may call back into Python
             }
             unref();
             return;
         }
 #endif
-        delete internals_singleton_pp_;
+        {
+            std::lock_guard<std::mutex> lock(pp_set_mutex_);
+            pps_have_created_content_.erase(internals_singleton_pp_); // untrack deleted pp
+        }
+        delete internals_singleton_pp_; // may call back into Python
         unref();
     }
 
-private:
-    std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
-        error_scope err_scope;
-        dict state_dict = get_python_state_dict();
-        auto internals_obj
-            = reinterpret_steal<object>(dict_getitemstringref(state_dict.ptr(), holder_id_));
-        std::unique_ptr<InternalsType> *pp = nullptr;
-        if (internals_obj) {
-            void *raw_ptr = PyCapsule_GetPointer(internals_obj.ptr(), /*name=*/nullptr);
-            if (!raw_ptr) {
-                raise_from(PyExc_SystemError,
-                           "pybind11::detail::internals_pp_manager::get_pp_from_dict() FAILED");
-                throw error_already_set();
+    void create_pp_content_once(std::unique_ptr<InternalsType> *const pp) {
+        // Assume the GIL is held here. May call back into Python. We cannot hold the lock with our
+        // mutex here. So there may be multiple threads creating the content at the same time. Only
+        // one will install its content to pp below. Others will be freed when going out of scope.
+        auto tmp = std::unique_ptr<InternalsType>(new InternalsType());
+
+        {
+            // Lock scope must not include Python calls, which may require the GIL and cause
+            // deadlocks.
+            std::lock_guard<std::mutex> lock(pp_set_mutex_);
+
+            if (*pp) {
+                // Already created in another thread.
+                return;
             }
-            pp = reinterpret_cast<std::unique_ptr<InternalsType> *>(raw_ptr);
-            if (on_fetch_ && pp) {
-                on_fetch_(pp->get());
+
+            // At this point, pp->get() is nullptr.
+            // The content is either not yet created, or was previously destroyed via pp->reset().
+
+            // Detect re-creation of internals after destruction during interpreter shutdown.
+            // If pybind11 code (e.g., tp_traverse/tp_clear calling py::cast) runs after internals
+            // have been destroyed, a new empty internals would be created, causing type lookup
+            // failures. See also get_or_create_pp_in_state_dict() comments.
+            if (pps_have_created_content_.find(pp) != pps_have_created_content_.end()) {
+                pybind11_fail(
+                    "pybind11::detail::internals_pp_manager::create_pp_content_once() "
+                    "FAILED: reentrant call detected while fetching pybind11 internals!");
             }
-        } else {
-            pp = new std::unique_ptr<InternalsType>;
-            // NOLINTNEXTLINE(bugprone-casting-through-void)
-            state_dict[holder_id_] = capsule(reinterpret_cast<void *>(pp));
+
+            // Each interpreter can only create its internals once.
+            pps_have_created_content_.insert(pp);
+            // Install the created content.
+            pp->swap(tmp);
+        }
+    }
+
+private:
+    internals_pp_manager(char const *id, on_fetch_function *on_fetch)
+        : holder_id_(id), on_fetch_(on_fetch) {}
+
+    std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
+        // The `unique_ptr<InternalsType>` is intentionally leaked on interpreter shutdown.
+        // Once an instance is created, it will never be deleted until the process exits (compare
+        // to interpreter shutdown in multiple-interpreter scenarios).
+        // We cannot guarantee the destruction order of capsules in the interpreter state dict on
+        // interpreter shutdown, so deleting internals too early could cause undefined behavior
+        // when other pybind11 objects access `get_internals()` during finalization (which would
+        // recreate empty internals). See also create_pp_content_once() above.
+        // See https://github.com/pybind/pybind11/pull/5958#discussion_r2717645230.
+        auto result = atomic_get_or_create_in_state_dict<std::unique_ptr<InternalsType>>(
+            holder_id_, /*dtor=*/nullptr /* leak the capsule content */);
+        auto *pp = result.first;
+        bool created = result.second;
+        // Only call on_fetch_ when fetching existing internals, not when creating new ones.
+        if (!created && on_fetch_ && pp) {
+            on_fetch_(pp->get());
         }
         return pp;
     }
 
-    char const *holder_id_ = nullptr;
-    on_fetch_function *on_fetch_ = nullptr;
 #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
-    thread_specific_storage<PyInterpreterState> last_istate_;
-    thread_specific_storage<std::unique_ptr<InternalsType>> internals_tls_p_;
+    static PyInterpreterState *&last_istate_tls() {
+        static thread_local PyInterpreterState *last_istate = nullptr;
+        return last_istate;
+    }
+
+    static std::unique_ptr<InternalsType> *&internals_p_tls() {
+        static thread_local std::unique_ptr<InternalsType> *internals_p = nullptr;
+        return internals_p;
+    }
 #endif
-    std::unique_ptr<InternalsType> *internals_singleton_pp_;
+
+    char const *holder_id_ = nullptr;
+    on_fetch_function *on_fetch_ = nullptr;
+    // Pointer-to-pointer to the singleton internals for the first seen interpreter (may not be the
+    // main interpreter)
+    std::unique_ptr<InternalsType> *internals_singleton_pp_ = nullptr;
+
+    // Track pointer-to-pointers whose internals have been created, to detect re-entrancy.
+    // Use instance member over static due to singleton pattern of this class.
+    std::unordered_set<std::unique_ptr<InternalsType> *> pps_have_created_content_;
+    std::mutex pp_set_mutex_;
 };
 
 // If We loaded the internals through `state_dict`, our `error_already_set`
@@ -623,22 +855,28 @@
 #else
 #    define ON_FETCH_FN &check_internals_local_exception_translator
 #endif
-    static internals_pp_manager<internals> internals_pp_manager(PYBIND11_INTERNALS_ID,
-                                                                ON_FETCH_FN);
+    return internals_pp_manager<internals>::get_instance(PYBIND11_INTERNALS_ID, ON_FETCH_FN);
 #undef ON_FETCH_FN
-    return internals_pp_manager;
 }
 
 /// Return a reference to the current `internals` data
 PYBIND11_NOINLINE internals &get_internals() {
     auto &ppmgr = get_internals_pp_manager();
-    auto &internals_ptr = *ppmgr.get_pp();
+    auto *pp = ppmgr.get_pp();
+    if (!pp) {
+        pybind11_fail("get_internals: get_pp() returned nullptr");
+    }
+    auto &internals_ptr = *pp;
     if (!internals_ptr) {
         // Slow path, something needs fetched from the state dict or created
         gil_scoped_acquire_simple gil;
         error_scope err_scope;
-        internals_ptr.reset(new internals());
 
+        ppmgr.create_pp_content_once(&internals_ptr);
+
+        if (!internals_ptr) {
+            pybind11_fail("get_internals: create_pp_content_once() produced nullptr");
+        }
         if (!internals_ptr->instance_base) {
             // This calls get_internals, so cannot be called from within the internals constructor
             // called above because internals_ptr must be set before get_internals is called again
@@ -648,15 +886,46 @@
     return *internals_ptr;
 }
 
+/// Return the PyObject* for the internals capsule (borrowed reference).
+/// Returns nullptr if the capsule doesn't exist yet.
+inline PyObject *get_internals_capsule() {
+    auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
+    return dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID);
+}
+
+/// Return the key used for local_internals in the state dict.
+/// This function ensures a consistent key is used across all call sites within the same
+/// compilation unit. The key includes the address of a static variable to make it unique per
+/// module (DSO), matching the behavior of get_local_internals_pp_manager().
+inline const std::string &get_local_internals_key() {
+    static const std::string key
+        = PYBIND11_MODULE_LOCAL_ID + std::to_string(reinterpret_cast<uintptr_t>(&key));
+    return key;
+}
+
+/// Return the PyObject* for the local_internals capsule (borrowed reference).
+/// Returns nullptr if the capsule doesn't exist yet.
+inline PyObject *get_local_internals_capsule() {
+    const auto &key = get_local_internals_key();
+    auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
+    return dict_getitemstring(state_dict.ptr(), key.c_str());
+}
+
+inline void ensure_internals() {
+    pybind11::detail::get_internals_pp_manager().unref();
+#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
+    if (PyInterpreterState_Get() != PyInterpreterState_Main()) {
+        has_seen_non_main_interpreter() = true;
+    }
+#endif
+    pybind11::detail::get_internals();
+}
+
 inline internals_pp_manager<local_internals> &get_local_internals_pp_manager() {
-    // Use the address of this static itself as part of the key, so that the value is uniquely tied
+    // Use the address of a static variable as part of the key, so that the value is uniquely tied
     // to where the module is loaded in memory
-    static const std::string this_module_idstr
-        = PYBIND11_MODULE_LOCAL_ID
-          + std::to_string(reinterpret_cast<uintptr_t>(&this_module_idstr));
-    static internals_pp_manager<local_internals> local_internals_pp_manager(
-        this_module_idstr.c_str(), nullptr);
-    return local_internals_pp_manager;
+    return internals_pp_manager<local_internals>::get_instance(get_local_internals_key().c_str(),
+                                                               nullptr);
 }
 
 /// Works like `get_internals`, but for things which are locally registered.
@@ -664,13 +933,16 @@
     auto &ppmgr = get_local_internals_pp_manager();
     auto &internals_ptr = *ppmgr.get_pp();
     if (!internals_ptr) {
-        internals_ptr.reset(new local_internals());
+        gil_scoped_acquire_simple gil;
+        error_scope err_scope;
+
+        ppmgr.create_pp_content_once(&internals_ptr);
     }
     return *internals_ptr;
 }
 
 #ifdef Py_GIL_DISABLED
-#    define PYBIND11_LOCK_INTERNALS(internals) std::unique_lock<pymutex> lock((internals).mutex)
+#    define PYBIND11_LOCK_INTERNALS(internals) pycritical_section lock((internals).mutex)
 #else
 #    define PYBIND11_LOCK_INTERNALS(internals)
 #endif
@@ -683,12 +955,23 @@
 }
 
 template <typename F>
+inline void with_internals_if_internals(const F &cb) {
+    auto &ppmgr = get_internals_pp_manager();
+    auto &internals_ptr = *ppmgr.get_pp();
+    if (internals_ptr) {
+        auto &internals = *internals_ptr;
+        PYBIND11_LOCK_INTERNALS(internals);
+        cb(internals);
+    }
+}
+
+template <typename F>
 inline auto with_exception_translators(const F &cb)
     -> decltype(cb(get_internals().registered_exception_translators,
                    get_local_internals().registered_exception_translators)) {
     auto &internals = get_internals();
 #ifdef Py_GIL_DISABLED
-    std::unique_lock<pymutex> lock((internals).exception_translator_mutex);
+    pycritical_section lock((internals).exception_translator_mutex);
 #endif
     auto &local_internals = get_local_internals();
     return cb(internals.registered_exception_translators,
diff -Nru pybind11-3.0.1/include/pybind11/detail/native_enum_data.h pybind11-3.0.4/include/pybind11/detail/native_enum_data.h
--- pybind11-3.0.1/include/pybind11/detail/native_enum_data.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/native_enum_data.h	2026-04-19 06:01:51.000000000 +0300
@@ -22,17 +22,36 @@
     return "pybind11::native_enum<...>(\"" + enum_name_encoded + "\", ...): MISSING .finalize()";
 }
 
+// Internals for pybind11::native_enum; one native_enum_data object exists
+// inside each pybind11::native_enum and lives only for the duration of the
+// native_enum binding statement.
 class native_enum_data {
 public:
-    native_enum_data(const object &parent_scope,
+    native_enum_data(handle parent_scope_,
                      const char *enum_name,
                      const char *native_type_name,
                      const char *class_doc,
-                     const std::type_index &enum_type_index)
+                     const native_enum_record &enum_record_)
         : enum_name_encoded{enum_name}, native_type_name_encoded{native_type_name},
-          enum_type_index{enum_type_index}, parent_scope(parent_scope), enum_name{enum_name},
+          enum_type_index{*enum_record_.cpptype},
+          parent_scope(reinterpret_borrow<object>(parent_scope_)), enum_name{enum_name},
           native_type_name{native_type_name}, class_doc(class_doc), export_values_flag{false},
-          finalize_needed{false} {}
+          finalize_needed{false} {
+        // Create the enum record capsule. It will be installed on the enum
+        // type object during finalize(). Its destructor removes the enum
+        // mapping from our internals, so that we won't try to convert to an
+        // enum type that's been destroyed.
+        enum_record = capsule(
+            new native_enum_record{enum_record_},
+            native_enum_record::attribute_name(),
+            +[](void *record_) {
+                auto *record = static_cast<native_enum_record *>(record_);
+                with_internals([&](internals &internals) {
+                    internals.native_enum_type_map.erase(*record->cpptype);
+                });
+                delete record;
+            });
+    }
 
     void finalize();
 
@@ -71,6 +90,7 @@
     str enum_name;
     str native_type_name;
     std::string class_doc;
+    capsule enum_record;
 
 protected:
     list members;
@@ -81,12 +101,6 @@
     bool finalize_needed : 1;
 };
 
-inline void global_internals_native_enum_type_map_set_item(const std::type_index &enum_type_index,
-                                                           PyObject *py_enum) {
-    with_internals(
-        [&](internals &internals) { internals.native_enum_type_map[enum_type_index] = py_enum; });
-}
-
 inline handle
 global_internals_native_enum_type_map_get_item(const std::type_index &enum_type_index) {
     return with_internals([&](internals &internals) {
@@ -202,7 +216,11 @@
     for (auto doc : member_docs) {
         py_enum[doc[int_(0)]].attr("__doc__") = doc[int_(1)];
     }
-    global_internals_native_enum_type_map_set_item(enum_type_index, py_enum.release().ptr());
+
+    py_enum.attr(native_enum_record::attribute_name()) = enum_record;
+    with_internals([&](internals &internals) {
+        internals.native_enum_type_map[enum_type_index] = py_enum.ptr();
+    });
 }
 
 PYBIND11_NAMESPACE_END(detail)
diff -Nru pybind11-3.0.1/include/pybind11/detail/pybind11_namespace_macros.h pybind11-3.0.4/include/pybind11/detail/pybind11_namespace_macros.h
--- pybind11-3.0.1/include/pybind11/detail/pybind11_namespace_macros.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/pybind11_namespace_macros.h	2026-04-19 06:01:51.000000000 +0300
@@ -16,12 +16,7 @@
 // possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please
 // only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla
 // Clang.
-#if defined(_MSC_VER)
-#    define PYBIND11_COMPILER_MSVC
-#    define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__)
-#    define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push))
-#    define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop))
-#elif defined(__INTEL_COMPILER)
+#if defined(__INTEL_COMPILER)
 #    define PYBIND11_COMPILER_INTEL
 #    define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
 #    define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push)
@@ -36,6 +31,11 @@
 #    define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
 #    define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push)
 #    define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop)
+#elif defined(_MSC_VER) // Must be after the clang branch because clang-cl also defines _MSC_VER
+#    define PYBIND11_COMPILER_MSVC
+#    define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__)
+#    define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push))
+#    define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop))
 #endif
 
 #ifdef PYBIND11_COMPILER_MSVC
diff -Nru pybind11-3.0.1/include/pybind11/detail/struct_smart_holder.h pybind11-3.0.4/include/pybind11/detail/struct_smart_holder.h
--- pybind11-3.0.1/include/pybind11/detail/struct_smart_holder.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/struct_smart_holder.h	2026-04-19 06:01:51.000000000 +0300
@@ -339,22 +339,42 @@
 
     template <typename T, typename D>
     static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
-                                        void *void_ptr = nullptr) {
+                                        void *mi_subobject_ptr = nullptr) {
         smart_holder hld;
         hld.rtti_uqp_del = &typeid(D);
         hld.vptr_is_using_std_default_delete = uqp_del_is_std_default_delete<T, D>();
-        guarded_delete gd{nullptr, false};
-        if (hld.vptr_is_using_std_default_delete) {
-            gd = make_guarded_std_default_delete<T>(true);
-        } else {
-            gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
-        }
-        if (void_ptr != nullptr) {
-            hld.vptr.reset(void_ptr, std::move(gd));
+
+        // Build the owning control block on the *real object start* (T*).
+        guarded_delete gd
+            = hld.vptr_is_using_std_default_delete
+                  ? make_guarded_std_default_delete<T>(true)
+                  : make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
+        // Critical: construct owner with pointer we intend to delete
+        std::shared_ptr<T> owner(unq_ptr.get(), std::move(gd));
+        // Relinquish ownership only after successful construction of owner
+        (void) unq_ptr.release();
+
+        // Publish either the MI/VI subobject pointer (if provided) or the full object.
+        // Why this is needed:
+        //   * The `owner` shared_ptr must always manage the true object start (T*).
+        //     That ensures the deleter is invoked on a valid object header, so the
+        //     virtual destructor can dispatch safely (critical on MSVC with virtual
+        //     inheritance, where base subobjects are not at offset 0).
+        //   * However, pybind11 needs to *register* and expose the subobject pointer
+        //     appropriate for the type being bound.
+        //     This pointer may differ from the T* object start under multiple/virtual
+        //     inheritance.
+        // This is achieved by using an aliasing shared_ptr<void>:
+        //   - `owner` retains lifetime of the actual T* object start for deletion.
+        //   - `vptr` points at the adjusted subobject (mi_subobject_ptr), giving
+        //     Python the correct identity/registration address.
+        // If no subobject pointer is passed, we simply publish the full object.
+        if (mi_subobject_ptr) {
+            hld.vptr = std::shared_ptr<void>(owner, mi_subobject_ptr);
         } else {
-            hld.vptr.reset(unq_ptr.get(), std::move(gd));
+            hld.vptr = std::static_pointer_cast<void>(owner);
         }
-        (void) unq_ptr.release();
+
         hld.is_populated = true;
         return hld;
     }
diff -Nru pybind11-3.0.1/include/pybind11/detail/type_caster_base.h pybind11-3.0.4/include/pybind11/detail/type_caster_base.h
--- pybind11-3.0.1/include/pybind11/detail/type_caster_base.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/type_caster_base.h	2026-04-19 06:01:51.000000000 +0300
@@ -42,24 +42,40 @@
 /// Adding a patient will keep it alive up until the enclosing function returns.
 class loader_life_support {
 private:
+    // Thread-local top-of-stack for loader_life_support frames (linked via parent).
+    // Observation: loader_life_support needs to be thread-local,
+    // but we don't need to go to extra effort to keep it
+    // per-interpreter (i.e., by putting it in internals) since
+    // individual function calls are already isolated to a single
+    // interpreter, even though they could potentially call into a
+    // different interpreter later in the same call chain.  This
+    // saves a significant cost per function call spent in
+    // loader_life_support destruction.
+    // Note for future C++17 simplification:
+    // inline static thread_local loader_life_support *tls_current_frame = nullptr;
+    static loader_life_support *&tls_current_frame() {
+        static thread_local loader_life_support *frame_ptr = nullptr;
+        return frame_ptr;
+    }
+
     loader_life_support *parent = nullptr;
     std::unordered_set<PyObject *> keep_alive;
 
 public:
     /// A new patient frame is created when a function is entered
     loader_life_support() {
-        auto &stack_top = get_internals().loader_life_support_tls;
-        parent = stack_top.get();
-        stack_top = this;
+        auto &frame = tls_current_frame();
+        parent = frame;
+        frame = this;
     }
 
     /// ... and destroyed after it returns
     ~loader_life_support() {
-        auto &stack_top = get_internals().loader_life_support_tls;
-        if (stack_top.get() != this) {
+        auto &frame = tls_current_frame();
+        if (frame != this) {
             pybind11_fail("loader_life_support: internal error");
         }
-        stack_top = parent;
+        frame = parent;
         for (auto *item : keep_alive) {
             Py_DECREF(item);
         }
@@ -68,7 +84,7 @@
     /// This can only be used inside a pybind11-bound function, either by `argument_loader`
     /// at argument preparation time or by `py::cast()` at execution time.
     PYBIND11_NOINLINE static void add_patient(handle h) {
-        loader_life_support *frame = get_internals().loader_life_support_tls.get();
+        loader_life_support *frame = tls_current_frame();
         if (!frame) {
             // NOTE: It would be nice to include the stack frames here, as this indicates
             // use of pybind11::cast<> outside the normal call framework, finding such
@@ -109,7 +125,7 @@
     assert(bases.empty());
     std::vector<PyTypeObject *> check;
     for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
-        check.push_back((PyTypeObject *) parent.ptr());
+        check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
     }
     auto const &type_dict = get_internals().registered_types_py;
     for (size_t i = 0; i < check.size(); i++) {
@@ -152,7 +168,7 @@
                 i--;
             }
             for (handle parent : reinterpret_borrow<tuple>(type->tp_bases)) {
-                check.push_back((PyTypeObject *) parent.ptr());
+                check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
             }
         }
     }
@@ -189,35 +205,73 @@
     return bases.front();
 }
 
-inline detail::type_info *get_local_type_info(const std::type_index &tp) {
-    auto &locals = get_local_internals().registered_types_cpp;
-    auto it = locals.find(tp);
+inline detail::type_info *get_local_type_info_lock_held(const std::type_info &tp) {
+    const auto &locals = get_local_internals().registered_types_cpp;
+    auto it = locals.find(&tp);
     if (it != locals.end()) {
         return it->second;
     }
     return nullptr;
 }
 
-inline detail::type_info *get_global_type_info(const std::type_index &tp) {
-    return with_internals([&](internals &internals) {
-        detail::type_info *type_info = nullptr;
-        auto &types = internals.registered_types_cpp;
-        auto it = types.find(tp);
-        if (it != types.end()) {
-            type_info = it->second;
-        }
-        return type_info;
-    });
+inline detail::type_info *get_local_type_info(const std::type_info &tp) {
+    // NB: internals and local_internals share a single mutex
+    PYBIND11_LOCK_INTERNALS(get_internals());
+    return get_local_type_info_lock_held(tp);
+}
+
+inline detail::type_info *get_global_type_info_lock_held(const std::type_info &tp) {
+    // This is a two-level lookup. Hopefully we find the type info in
+    // registered_types_cpp_fast, but if not we try
+    // registered_types_cpp and fill registered_types_cpp_fast for
+    // next time.
+    detail::type_info *type_info = nullptr;
+    auto &internals = get_internals();
+#if PYBIND11_INTERNALS_VERSION >= 12
+    auto &fast_types = internals.registered_types_cpp_fast;
+#endif
+    auto &types = internals.registered_types_cpp;
+#if PYBIND11_INTERNALS_VERSION >= 12
+    auto fast_it = fast_types.find(&tp);
+    if (fast_it != fast_types.end()) {
+#    ifndef NDEBUG
+        auto types_it = types.find(std::type_index(tp));
+        assert(types_it != types.end());
+        assert(types_it->second == fast_it->second);
+#    endif
+        return fast_it->second;
+    }
+#endif // PYBIND11_INTERNALS_VERSION >= 12
+
+    auto it = types.find(std::type_index(tp));
+    if (it != types.end()) {
+#if PYBIND11_INTERNALS_VERSION >= 12
+        // We found the type in the slow map but not the fast one, so
+        // some other DSO added it (otherwise it would be in the fast
+        // map under &tp) and therefore we must be an alias. Record
+        // that.
+        it->second->alias_chain.push_front(&tp);
+        fast_types.emplace(&tp, it->second);
+#endif
+        type_info = it->second;
+    }
+    return type_info;
+}
+
+inline detail::type_info *get_global_type_info(const std::type_info &tp) {
+    PYBIND11_LOCK_INTERNALS(get_internals());
+    return get_global_type_info_lock_held(tp);
 }
 
 /// Return the type info for a given C++ type; on lookup failure can either throw or return
 /// nullptr.
-PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_index &tp,
+PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp,
                                                    bool throw_if_missing = false) {
-    if (auto *ltype = get_local_type_info(tp)) {
+    PYBIND11_LOCK_INTERNALS(get_internals());
+    if (auto *ltype = get_local_type_info_lock_held(tp)) {
         return ltype;
     }
-    if (auto *gtype = get_global_type_info(tp)) {
+    if (auto *gtype = get_global_type_info_lock_held(tp)) {
         return gtype;
     }
 
@@ -232,14 +286,14 @@
 
 PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) {
     detail::type_info *type_info = get_type_info(tp, throw_if_missing);
-    return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
+    return handle(type_info ? (reinterpret_cast<PyObject *>(type_info->type)) : nullptr);
 }
 
 inline bool try_incref(PyObject *obj) {
     // Tries to increment the reference count of an object if it's not zero.
-    // TODO: Use PyUnstable_TryIncref when available.
-    // See https://github.com/python/cpython/issues/128844
-#ifdef Py_GIL_DISABLED
+#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A4
+    return PyUnstable_TryIncRef(obj);
+#elif defined(Py_GIL_DISABLED)
     // See
     // https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761
     uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local);
@@ -452,7 +506,7 @@
         // efficient for small allocations like the one we're doing here;
         // for larger allocations they are just wrappers around malloc.
         // TODO: is this still true for pure Python 3.6?
-        nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *));
+        nonsimple.values_and_holders = static_cast<void **>(PyMem_Calloc(space, sizeof(void *)));
         if (!nonsimple.values_and_holders) {
             throw std::bad_alloc();
         }
@@ -483,7 +537,7 @@
         for (auto it = range.first; it != range.second; ++it) {
             for (const auto &vh : values_and_holders(it->second)) {
                 if (vh.type == type) {
-                    return handle((PyObject *) it->second);
+                    return handle(reinterpret_cast<PyObject *>(it->second));
                 }
             }
         }
@@ -491,6 +545,74 @@
     });
 }
 
+// Information about how type_caster_generic::cast() can obtain its source object
+struct cast_sources {
+    // A type-erased pointer and the type it points to
+    struct raw_source {
+        const void *cppobj;
+        const std::type_info *cpptype;
+    };
+
+    // A C++ pointer and the Python type info we will convert it to;
+    // we expect that cppobj points to something of type tinfo->cpptype
+    struct resolved_source {
+        const void *cppobj;
+        const type_info *tinfo;
+    };
+
+    // Use the given pointer with its compile-time type, possibly downcast
+    // via polymorphic_type_hook()
+    template <typename itype>
+    explicit cast_sources(const itype *ptr);
+
+    // Use the given pointer and type
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cast_sources(const raw_source &orig) : original(orig) { result = resolve(); }
+
+    // Use the given object and pybind11 type info. NB: if tinfo is null,
+    // this does not provide enough information to use a foreign type or
+    // to render a useful error message
+    cast_sources(const void *obj, const detail::type_info *tinfo)
+        : original{obj, tinfo ? tinfo->cpptype : nullptr}, result{obj, tinfo} {}
+
+    // The object passed to cast(), with its static type.
+    // original.type must not be null if resolve() will be called.
+    // original.obj may be null if we're converting nullptr to a Python None
+    raw_source original;
+
+    // A more-derived version of `original` provided by a
+    // polymorphic_type_hook. downcast.type may be null if this is not
+    // a relevant concept for the current cast.
+    raw_source downcast{};
+
+    // The source to use for this cast, and the corresponding pybind11
+    // type_info. If the type_info is null, then pybind11 doesn't know
+    // about this type.
+    resolved_source result;
+
+    // Returns true if the cast will use a pybind11 type that uses
+    // a smart holder.
+    bool creates_smart_holder() const {
+        return result.tinfo != nullptr
+               && result.tinfo->holder_enum_v == detail::holder_enum_t::smart_holder;
+    }
+
+private:
+    resolved_source resolve() {
+        if (downcast.cpptype) {
+            if (same_type(*original.cpptype, *downcast.cpptype)) {
+                downcast.cpptype = nullptr;
+            } else if (const auto *tpi = get_type_info(*downcast.cpptype)) {
+                return {downcast.cppobj, tpi};
+            }
+        }
+        if (const auto *tpi = get_type_info(*original.cpptype)) {
+            return {original.cppobj, tpi};
+        }
+        return {nullptr, nullptr};
+    }
+};
+
 // Forward declarations
 void keep_alive_impl(handle nurse, handle patient);
 inline PyObject *make_new_instance(PyTypeObject *type);
@@ -553,16 +675,18 @@
 handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
                                     return_value_policy policy,
                                     handle parent,
-                                    const std::pair<const void *, const type_info *> &st) {
+                                    const cast_sources::resolved_source &cs) {
     if (policy == return_value_policy::copy) {
         throw cast_error("return_value_policy::copy is invalid for unique_ptr.");
     }
     if (!src) {
         return none().release();
     }
-    void *src_raw_void_ptr = const_cast<void *>(st.first);
-    assert(st.second != nullptr);
-    const detail::type_info *tinfo = st.second;
+    // cs.cppobj is the subobject pointer appropriate for tinfo (may differ from src.get()
+    // under MI/VI). Use this for Python identity/registration, but keep ownership on T*.
+    void *src_raw_void_ptr = const_cast<void *>(cs.cppobj);
+    assert(cs.tinfo != nullptr);
+    const detail::type_info *tinfo = cs.tinfo;
     if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
         auto *self_life_support = tinfo->get_trampoline_self_life_support(src.get());
         if (self_life_support != nullptr) {
@@ -609,20 +733,20 @@
 handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src,
                                     return_value_policy policy,
                                     handle parent,
-                                    const std::pair<const void *, const type_info *> &st) {
+                                    const cast_sources::resolved_source &cs) {
     return smart_holder_from_unique_ptr(
         std::unique_ptr<T, D>(const_cast<T *>(src.release()),
                               std::move(src.get_deleter())), // Const2Mutbl
         policy,
         parent,
-        st);
+        cs);
 }
 
 template <typename T>
 handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
                                     return_value_policy policy,
                                     handle parent,
-                                    const std::pair<const void *, const type_info *> &st) {
+                                    const cast_sources::resolved_source &cs) {
     switch (policy) {
         case return_value_policy::automatic:
         case return_value_policy::automatic_reference:
@@ -641,10 +765,11 @@
         return none().release();
     }
 
-    auto src_raw_ptr = src.get();
-    assert(st.second != nullptr);
-    void *src_raw_void_ptr = static_cast<void *>(src_raw_ptr);
-    const detail::type_info *tinfo = st.second;
+    // cs.cppobj is the subobject pointer appropriate for tinfo (may differ from src.get()
+    // under MI/VI). Use this for Python identity/registration, but keep ownership on T*.
+    void *src_raw_void_ptr = const_cast<void *>(cs.cppobj);
+    assert(cs.tinfo != nullptr);
+    const detail::type_info *tinfo = cs.tinfo;
     if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
         // PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder.
         // PYBIND11:REMINDER: MISSING: keep_alive.
@@ -657,8 +782,7 @@
     void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr();
     valueptr = src_raw_void_ptr;
 
-    auto smhldr
-        = smart_holder::from_shared_ptr(std::shared_ptr<void>(src, const_cast<void *>(st.first)));
+    auto smhldr = smart_holder::from_shared_ptr(std::shared_ptr<void>(src, src_raw_void_ptr));
     tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
 
     if (policy == return_value_policy::reference_internal) {
@@ -672,11 +796,11 @@
 handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src,
                                     return_value_policy policy,
                                     handle parent,
-                                    const std::pair<const void *, const type_info *> &st) {
+                                    const cast_sources::resolved_source &cs) {
     return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(src), // Const2Mutbl
                                         policy,
                                         parent,
-                                        st);
+                                        cs);
 }
 
 struct shared_ptr_parent_life_support {
@@ -869,21 +993,51 @@
 
     bool load(handle src, bool convert) { return load_impl<type_caster_generic>(src, convert); }
 
-    PYBIND11_NOINLINE static handle cast(const void *_src,
+    static handle cast(const void *src,
+                       return_value_policy policy,
+                       handle parent,
+                       const detail::type_info *tinfo,
+                       void *(*copy_constructor)(const void *),
+                       void *(*move_constructor)(const void *),
+                       const void *existing_holder = nullptr) {
+        cast_sources srcs{src, tinfo};
+        return cast(srcs, policy, parent, copy_constructor, move_constructor, existing_holder);
+    }
+
+    static handle cast_non_owning(const cast_sources &srcs,
+                                  return_value_policy policy,
+                                  handle parent,
+                                  const void *existing_holder = nullptr) {
+        // Reference-like policies alias an existing C++ object instead of creating
+        // a new one, so copy/move constructor callbacks must remain null here.
+        assert(policy == return_value_policy::reference
+               || policy == return_value_policy::reference_internal
+               || policy == return_value_policy::automatic_reference);
+        return cast(srcs, policy, parent, nullptr, nullptr, existing_holder);
+    }
+
+    PYBIND11_NOINLINE static handle cast(const cast_sources &srcs,
                                          return_value_policy policy,
                                          handle parent,
-                                         const detail::type_info *tinfo,
                                          void *(*copy_constructor)(const void *),
                                          void *(*move_constructor)(const void *),
                                          const void *existing_holder = nullptr) {
-        if (!tinfo) { // no type info: error will be set already
+        if (!srcs.result.tinfo) {
+            // No pybind11 type info. Raise an exception.
+            std::string tname = srcs.downcast.cpptype   ? srcs.downcast.cpptype->name()
+                                : srcs.original.cpptype ? srcs.original.cpptype->name()
+                                                        : "<unspecified>";
+            detail::clean_type_id(tname);
+            std::string msg = "Unregistered type : " + tname;
+            set_error(PyExc_TypeError, msg.c_str());
             return handle();
         }
 
-        void *src = const_cast<void *>(_src);
+        void *src = const_cast<void *>(srcs.result.cppobj);
         if (src == nullptr) {
             return none().release();
         }
+        const type_info *tinfo = srcs.result.tinfo;
 
         if (handle registered_inst = find_registered_python_instance(src, tinfo)) {
             return registered_inst;
@@ -1018,6 +1172,7 @@
         return false;
     }
     void check_holder_compat() {}
+    bool set_foreign_holder(handle) { return true; }
 
     PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) {
         auto caster = type_caster_generic(ti);
@@ -1056,14 +1211,14 @@
     // logic (without having to resort to virtual inheritance).
     template <typename ThisT>
     PYBIND11_NOINLINE bool load_impl(handle src, bool convert) {
+        auto &this_ = static_cast<ThisT &>(*this);
         if (!src) {
             return false;
         }
         if (!typeinfo) {
-            return try_load_foreign_module_local(src);
+            return try_load_foreign_module_local(src) && this_.set_foreign_holder(src);
         }
 
-        auto &this_ = static_cast<ThisT &>(*this);
         this_.check_holder_compat();
 
         PyTypeObject *srctype = Py_TYPE(src.ptr());
@@ -1129,13 +1284,13 @@
         if (typeinfo->module_local) {
             if (auto *gtype = get_global_type_info(*typeinfo->cpptype)) {
                 typeinfo = gtype;
-                return load(src, false);
+                return load_impl<ThisT>(src, false);
             }
         }
 
         // Global typeinfo has precedence over foreign module_local
         if (try_load_foreign_module_local(src)) {
-            return true;
+            return this_.set_foreign_holder(src);
         }
 
         // Custom converters didn't take None, now we convert None to nullptr.
@@ -1149,31 +1304,12 @@
         }
 
         if (convert && cpptype && this_.try_cpp_conduit(src)) {
-            return true;
+            return this_.set_foreign_holder(src);
         }
 
         return false;
     }
 
-    // Called to do type lookup and wrap the pointer and type in a pair when a dynamic_cast
-    // isn't needed or can't be used.  If the type is unknown, sets the error and returns a pair
-    // with .second = nullptr.  (p.first = nullptr is not an error: it becomes None).
-    PYBIND11_NOINLINE static std::pair<const void *, const type_info *>
-    src_and_type(const void *src,
-                 const std::type_info &cast_type,
-                 const std::type_info *rtti_type = nullptr) {
-        if (auto *tpi = get_type_info(cast_type)) {
-            return {src, const_cast<const type_info *>(tpi)};
-        }
-
-        // Not found, set error:
-        std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
-        detail::clean_type_id(tname);
-        std::string msg = "Unregistered type : " + tname;
-        set_error(PyExc_TypeError, msg.c_str());
-        return {nullptr, nullptr};
-    }
-
     const type_info *typeinfo = nullptr;
     const std::type_info *cpptype = nullptr;
     void *value = nullptr;
@@ -1468,6 +1604,20 @@
 
 PYBIND11_NAMESPACE_BEGIN(detail)
 
+template <typename itype>
+cast_sources::cast_sources(const itype *ptr) : original{ptr, &typeid(itype)} {
+    // If this is a base pointer to a derived type, and the derived type is
+    // registered with pybind11, we want to make the full derived object
+    // available. In the typical case where itype is polymorphic, we get the
+    // correct derived pointer (which may be != base pointer) by a dynamic_cast
+    // to most derived type. If itype is not polymorphic, a user-provided
+    // specialization of polymorphic_type_hook can do the same thing.
+    // If there is no downcast to perform, then the default hook will leave
+    // derived.type set to nullptr, which causes us to ignore derived.obj.
+    downcast.cppobj = polymorphic_type_hook<itype>::get(ptr, downcast.cpptype);
+    result = resolve();
+}
+
 /// Generic type caster for objects stored on the heap
 template <typename type>
 class type_caster_base : public type_caster_generic {
@@ -1479,6 +1629,13 @@
     type_caster_base() : type_caster_base(typeid(type)) {}
     explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) {}
 
+    // Wrap the generic cast_sources to be only constructible from the type
+    // that's correct in this context, so you can't use type_caster_base<A>
+    // to convert an unrelated B* to Python.
+    struct cast_sources : detail::cast_sources {
+        explicit cast_sources(const itype *ptr) : detail::cast_sources(ptr) {}
+    };
+
     static handle cast(const itype &src, return_value_policy policy, handle parent) {
         if (policy == return_value_policy::automatic
             || policy == return_value_policy::automatic_reference) {
@@ -1491,50 +1648,25 @@
         return cast(std::addressof(src), return_value_policy::move, parent);
     }
 
-    // Returns a (pointer, type_info) pair taking care of necessary type lookup for a
-    // polymorphic type (using RTTI by default, but can be overridden by specializing
-    // polymorphic_type_hook). If the instance isn't derived, returns the base version.
-    static std::pair<const void *, const type_info *> src_and_type(const itype *src) {
-        const auto &cast_type = typeid(itype);
-        const std::type_info *instance_type = nullptr;
-        const void *vsrc = polymorphic_type_hook<itype>::get(src, instance_type);
-        if (instance_type && !same_type(cast_type, *instance_type)) {
-            // This is a base pointer to a derived type. If the derived type is registered
-            // with pybind11, we want to make the full derived object available.
-            // In the typical case where itype is polymorphic, we get the correct
-            // derived pointer (which may be != base pointer) by a dynamic_cast to
-            // most derived type. If itype is not polymorphic, we won't get here
-            // except via a user-provided specialization of polymorphic_type_hook,
-            // and the user has promised that no this-pointer adjustment is
-            // required in that case, so it's OK to use static_cast.
-            if (const auto *tpi = get_type_info(*instance_type)) {
-                return {vsrc, tpi};
-            }
-        }
-        // Otherwise we have either a nullptr, an `itype` pointer, or an unknown derived pointer,
-        // so don't do a cast
-        return type_caster_generic::src_and_type(src, cast_type, instance_type);
+    static handle cast(const itype *src, return_value_policy policy, handle parent) {
+        return cast(cast_sources{src}, policy, parent);
     }
 
-    static handle cast(const itype *src, return_value_policy policy, handle parent) {
-        auto st = src_and_type(src);
-        return type_caster_generic::cast(st.first,
+    static handle cast(const cast_sources &srcs, return_value_policy policy, handle parent) {
+        return type_caster_generic::cast(srcs,
                                          policy,
                                          parent,
-                                         st.second,
-                                         make_copy_constructor(src),
-                                         make_move_constructor(src));
+                                         make_copy_constructor((const itype *) nullptr),
+                                         make_move_constructor((const itype *) nullptr));
     }
 
     static handle cast_holder(const itype *src, const void *holder) {
-        auto st = src_and_type(src);
-        return type_caster_generic::cast(st.first,
-                                         return_value_policy::take_ownership,
-                                         {},
-                                         st.second,
-                                         nullptr,
-                                         nullptr,
-                                         holder);
+        return cast_holder(cast_sources{src}, holder);
+    }
+
+    static handle cast_holder(const cast_sources &srcs, const void *holder) {
+        auto policy = return_value_policy::take_ownership;
+        return type_caster_generic::cast(srcs, policy, {}, nullptr, nullptr, holder);
     }
 
     template <typename T>
@@ -1580,7 +1712,7 @@
 
 PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
     if (auto *type_data = get_type_info(ti)) {
-        handle th((PyObject *) type_data->type);
+        handle th(reinterpret_cast<PyObject *>(type_data->type));
         return th.attr("__module__").cast<std::string>() + '.'
                + th.attr("__qualname__").cast<std::string>();
     }
diff -Nru pybind11-3.0.1/include/pybind11/detail/value_and_holder.h pybind11-3.0.4/include/pybind11/detail/value_and_holder.h
--- pybind11-3.0.1/include/pybind11/detail/value_and_holder.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/detail/value_and_holder.h	2026-04-19 06:01:51.000000000 +0300
@@ -54,7 +54,8 @@
         } else if (v) {
             inst->nonsimple.status[index] |= instance::status_holder_constructed;
         } else {
-            inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed;
+            inst->nonsimple.status[index]
+                &= static_cast<std::uint8_t>(~instance::status_holder_constructed);
         }
     }
     bool instance_registered() const {
@@ -69,7 +70,8 @@
         } else if (v) {
             inst->nonsimple.status[index] |= instance::status_instance_registered;
         } else {
-            inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered;
+            inst->nonsimple.status[index]
+                &= static_cast<std::uint8_t>(~instance::status_instance_registered);
         }
     }
 };
diff -Nru pybind11-3.0.1/include/pybind11/eigen/tensor.h pybind11-3.0.4/include/pybind11/eigen/tensor.h
--- pybind11-3.0.1/include/pybind11/eigen/tensor.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/eigen/tensor.h	2026-04-19 06:01:51.000000000 +0300
@@ -124,10 +124,9 @@
 template <typename Type, bool ShowDetails, bool NeedsWriteable = false>
 struct get_tensor_descriptor {
     static constexpr auto details
-        = const_name<NeedsWriteable>(", \"flags.writeable\"", "") + const_name
-              < static_cast<int>(Type::Layout)
-          == static_cast<int>(Eigen::RowMajor)
-                 > (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\"");
+        = const_name<NeedsWriteable>(", \"flags.writeable\"", "")
+          + const_name<static_cast<int>(Type::Layout) == static_cast<int>(Eigen::RowMajor)>(
+              ", \"flags.c_contiguous\"", ", \"flags.f_contiguous\"");
     static constexpr auto value
         = const_name("typing.Annotated[")
           + io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
diff -Nru pybind11-3.0.1/include/pybind11/embed.h pybind11-3.0.4/include/pybind11/embed.h
--- pybind11-3.0.1/include/pybind11/embed.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/embed.h	2026-04-19 06:01:51.000000000 +0300
@@ -58,7 +58,7 @@
 PYBIND11_WARNING_PUSH
 PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
 #define PYBIND11_EMBEDDED_MODULE(name, variable, ...)                                             \
-    PYBIND11_MODULE_PYINIT(name, {}, ##__VA_ARGS__)                                               \
+    PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__)                                                   \
     ::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)(                  \
         PYBIND11_TOSTRING(name), PYBIND11_CONCAT(PyInit_, name));                                 \
     PYBIND11_MODULE_EXEC(name, variable)
@@ -202,7 +202,7 @@
 #endif
 
     // There is exactly one interpreter alive currently.
-    detail::get_num_interpreters_seen() = 1;
+    detail::has_seen_non_main_interpreter() = false;
 }
 
 /** \rst
@@ -242,12 +242,12 @@
  \endrst */
 inline void finalize_interpreter() {
     // get rid of any thread-local interpreter cache that currently exists
-    if (detail::get_num_interpreters_seen() > 1) {
+    if (detail::has_seen_non_main_interpreter()) {
         detail::get_internals_pp_manager().unref();
         detail::get_local_internals_pp_manager().unref();
 
-        // We know there can be no other interpreter alive now, so we can lower the count
-        detail::get_num_interpreters_seen() = 1;
+        // We know there can be no other interpreter alive now
+        detail::has_seen_non_main_interpreter() = false;
     }
 
     // Re-fetch the internals pointer-to-pointer (but not the internals itself, which might not
@@ -265,8 +265,8 @@
     // avoid undefined behaviors when initializing another interpreter
     detail::get_local_internals_pp_manager().destroy();
 
-    // We know there is no interpreter alive now, so we can reset the count
-    detail::get_num_interpreters_seen() = 0;
+    // We know there is no interpreter alive now, so we can reset the multi-flag
+    detail::has_seen_non_main_interpreter() = false;
 }
 
 /** \rst
diff -Nru pybind11-3.0.1/include/pybind11/gil.h pybind11-3.0.4/include/pybind11/gil.h
--- pybind11-3.0.1/include/pybind11/gil.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/gil.h	2026-04-19 06:01:51.000000000 +0300
@@ -120,7 +120,11 @@
                 pybind11_fail("scoped_acquire::dec_ref(): internal error!");
             }
 #    endif
+            // Make sure that PyThreadState_Clear is not recursively called by finalizers.
+            // See issue #5827
+            ++tstate->gilstate_counter;
             PyThreadState_Clear(tstate);
+            --tstate->gilstate_counter;
             if (active) {
                 PyThreadState_DeleteCurrent();
             }
diff -Nru pybind11-3.0.1/include/pybind11/gil_safe_call_once.h pybind11-3.0.4/include/pybind11/gil_safe_call_once.h
--- pybind11-3.0.1/include/pybind11/gil_safe_call_once.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/gil_safe_call_once.h	2026-04-19 06:01:51.000000000 +0300
@@ -3,17 +3,31 @@
 #pragma once
 
 #include "detail/common.h"
+#include "detail/internals.h"
 #include "gil.h"
 
 #include <cassert>
 #include <mutex>
 
-#ifdef Py_GIL_DISABLED
+#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
 #    include <atomic>
 #endif
+#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
+#    include <cstdint>
+#    include <memory>
+#    include <string>
+#endif
 
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 
+PYBIND11_NAMESPACE_BEGIN(detail)
+#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
+using atomic_bool = std::atomic_bool;
+#else
+using atomic_bool = bool;
+#endif
+PYBIND11_NAMESPACE_END(detail)
+
 // Use the `gil_safe_call_once_and_store` class below instead of the naive
 //
 //   static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE!
@@ -48,12 +62,23 @@
 // functions, which is usually the case.
 //
 // For in-depth background, see docs/advanced/deadlock.md
+#ifndef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
+// Subinterpreter support is disabled.
+// In this case, we can store the result globally, because there is only a single interpreter.
+//
+// The life span of the stored result is the entire process lifetime. It is leaked on process
+// termination to avoid destructor calls after the Python interpreter was finalized.
 template <typename T>
 class gil_safe_call_once_and_store {
 public:
     // PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
+    //
+    // NOTE: The second parameter (finalize callback) is intentionally unused when subinterpreter
+    // support is disabled. In that case, storage is process-global and intentionally leaked to
+    // avoid calling destructors after the Python interpreter has been finalized.
     template <typename Callable>
-    gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
+    gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
+                                                             void (*)(T &) /*unused*/ = nullptr) {
         if (!is_initialized_) { // This read is guarded by the GIL.
             // Multiple threads may enter here, because the GIL is released in the next line and
             // CPython API calls in the `fn()` call below may release and reacquire the GIL.
@@ -74,29 +99,175 @@
     T &get_stored() {
         assert(is_initialized_);
         PYBIND11_WARNING_PUSH
-#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
+#    if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
         // Needed for gcc 4.8.5
         PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
-#endif
+#    endif
         return *reinterpret_cast<T *>(storage_);
         PYBIND11_WARNING_POP
     }
 
     constexpr gil_safe_call_once_and_store() = default;
+    // The instance is a global static, so its destructor runs when the process
+    // is terminating. Therefore, do nothing here because the Python interpreter
+    // may have been finalized already.
     PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
 
+    // Disable copy and move operations.
+    gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
+    gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
+    gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
+    gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
+
 private:
+    // The global static storage (per-process) when subinterpreter support is disabled.
     alignas(T) char storage_[sizeof(T)] = {};
-    std::once_flag once_flag_ = {};
-#ifdef Py_GIL_DISABLED
-    std::atomic_bool
-#else
-    bool
-#endif
-        is_initialized_{false};
+    std::once_flag once_flag_;
+
     // The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
     // but the latter does not have the triviality properties of former,
     // therefore `std::optional` is not a viable alternative here.
+    detail::atomic_bool is_initialized_{false};
 };
+#else
+// Subinterpreter support is enabled.
+// In this case, we should store the result per-interpreter instead of globally, because each
+// subinterpreter has its own separate state. The cached result may not shareable across
+// interpreters (e.g., imported modules and their members).
+
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+template <typename T>
+struct call_once_storage {
+    alignas(T) char storage[sizeof(T)] = {};
+    std::once_flag once_flag;
+    void (*finalize)(T &) = nullptr;
+    std::atomic_bool is_initialized{false};
+
+    call_once_storage() = default;
+    ~call_once_storage() {
+        if (is_initialized) {
+            if (finalize != nullptr) {
+                finalize(*reinterpret_cast<T *>(storage));
+            } else {
+                reinterpret_cast<T *>(storage)->~T();
+            }
+        }
+    }
+    call_once_storage(const call_once_storage &) = delete;
+    call_once_storage(call_once_storage &&) = delete;
+    call_once_storage &operator=(const call_once_storage &) = delete;
+    call_once_storage &operator=(call_once_storage &&) = delete;
+};
+
+PYBIND11_NAMESPACE_END(detail)
+
+// Prefix for storage keys in the interpreter state dict.
+#    define PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX PYBIND11_INTERNALS_ID "_call_once_storage__"
+
+// The life span of the stored result is the entire interpreter lifetime. An additional
+// `finalize_fn` can be provided to clean up the stored result when the interpreter is destroyed.
+template <typename T>
+class gil_safe_call_once_and_store {
+public:
+    // PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
+    template <typename Callable>
+    gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
+                                                             void (*finalize_fn)(T &) = nullptr) {
+        if (!is_last_storage_valid()) {
+            // Multiple threads may enter here, because the GIL is released in the next line and
+            // CPython API calls in the `fn()` call below may release and reacquire the GIL.
+            gil_scoped_release gil_rel; // Needed to establish lock ordering.
+            // There can be multiple threads going through here.
+            storage_type *value = nullptr;
+            {
+                gil_scoped_acquire gil_acq; // Restore lock ordering.
+                // This function is thread-safe under free-threading.
+                value = get_or_create_storage_in_state_dict();
+            }
+            assert(value != nullptr);
+            std::call_once(value->once_flag, [&] {
+                // Only one thread will ever enter here.
+                gil_scoped_acquire gil_acq;
+                // fn may release, but will reacquire, the GIL.
+                ::new (value->storage) T(fn());
+                value->finalize = finalize_fn;
+                value->is_initialized = true;
+                last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
+                is_initialized_by_at_least_one_interpreter_ = true;
+            });
+            // All threads will observe `is_initialized_by_at_least_one_interpreter_` as true here.
+        }
+        // Intentionally not returning `T &` to ensure the calling code is self-documenting.
+        return *this;
+    }
+
+    // This must only be called after `call_once_and_store_result()` was called.
+    T &get_stored() {
+        T *result = last_storage_ptr_;
+        if (!is_last_storage_valid()) {
+            gil_scoped_acquire gil_acq;
+            auto *value = get_or_create_storage_in_state_dict();
+            result = last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
+        }
+        assert(result != nullptr);
+        return *result;
+    }
+
+    constexpr gil_safe_call_once_and_store() = default;
+    // The instance is a global static, so its destructor runs when the process
+    // is terminating. Therefore, do nothing here because the Python interpreter
+    // may have been finalized already.
+    PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
+
+    // Disable copy and move operations because the memory address is used as key.
+    gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
+    gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
+    gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
+    gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
+
+private:
+    using storage_type = detail::call_once_storage<T>;
+
+    // Indicator of fast path for single-interpreter case.
+    bool is_last_storage_valid() const {
+        return is_initialized_by_at_least_one_interpreter_
+               && !detail::has_seen_non_main_interpreter();
+    }
+
+    // Get the unique key for this storage instance in the interpreter's state dict.
+    // The return type should not be `py::str` because PyObject is interpreter-dependent.
+    std::string get_storage_key() const {
+        // The instance is expected to be global static, so using its address as unique identifier.
+        // The typical usage is like:
+        //
+        //   PYBIND11_CONSTINIT static gil_safe_call_once_and_store<T> storage;
+        //
+        return PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX
+               + std::to_string(reinterpret_cast<std::uintptr_t>(this));
+    }
+
+    // Get or create per-storage capsule in the current interpreter's state dict.
+    // The storage is interpreter-dependent and will not be shared across interpreters.
+    storage_type *get_or_create_storage_in_state_dict() {
+        return detail::atomic_get_or_create_in_state_dict<storage_type>(get_storage_key().c_str())
+            .first;
+    }
+
+    // No storage needed when subinterpreter support is enabled.
+    // The actual storage is stored in the per-interpreter state dict via
+    // `get_or_create_storage_in_state_dict()`.
+
+    // Fast local cache to avoid repeated lookups when there are no multiple interpreters.
+    // This is only valid if there is a single interpreter. Otherwise, it is not used.
+    // WARNING: We cannot use thread local cache similar to `internals_pp_manager::internals_p_tls`
+    //          because the thread local storage cannot be explicitly invalidated when interpreters
+    //          are destroyed (unlike `internals_pp_manager` which has explicit hooks for that).
+    T *last_storage_ptr_ = nullptr;
+    // This flag is true if the value has been initialized by any interpreter (may not be the
+    // current one).
+    detail::atomic_bool is_initialized_by_at_least_one_interpreter_{false};
+};
+#endif
 
 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/iostream.h pybind11-3.0.4/include/pybind11/iostream.h
--- pybind11-3.0.1/include/pybind11/iostream.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/iostream.h	2026-04-19 06:01:51.000000000 +0300
@@ -117,13 +117,36 @@
     int sync() override { return _sync(); }
 
 public:
+    // Minimum buffer size must accommodate the largest incomplete UTF-8 sequence
+    // (3 bytes) plus one position reserved for overflow(), i.e. 4 bytes total.
+    static constexpr size_t minimum_buffer_size = 4;
+
     explicit pythonbuf(const object &pyostream, size_t buffer_size = 1024)
-        : buf_size(buffer_size), d_buffer(new char[buf_size]), pywrite(pyostream.attr("write")),
+        : buf_size(buffer_size < minimum_buffer_size // ternary avoids C++14 std::max ODR-use of
+                                                     // static constexpr
+                       ? minimum_buffer_size
+                       : buffer_size),
+          d_buffer(new char[buf_size]), pywrite(pyostream.attr("write")),
           pyflush(pyostream.attr("flush")) {
         setp(d_buffer.get(), d_buffer.get() + buf_size - 1);
     }
 
-    pythonbuf(pythonbuf &&) = default;
+    pythonbuf(pythonbuf &&other) noexcept
+        : buf_size(other.buf_size), d_buffer(std::move(other.d_buffer)),
+          pywrite(std::move(other.pywrite)), pyflush(std::move(other.pyflush)) {
+        const auto pending = (other.pbase() != nullptr && other.pptr() != nullptr)
+                                 ? static_cast<int>(other.pptr() - other.pbase())
+                                 : 0;
+        if (d_buffer != nullptr) {
+            // Rebuild the put area from the transferred storage.
+            setp(d_buffer.get(), d_buffer.get() + buf_size - 1);
+            pbump(pending);
+        } else {
+            setp(nullptr, nullptr);
+        }
+        // Prevent the moved-from destructor from flushing through moved-out handles.
+        other.setp(nullptr, nullptr);
+    }
 
     /// Sync before destroy
     ~pythonbuf() override { _sync(); }
@@ -161,6 +184,7 @@
     std::streambuf *old;
     std::ostream &costream;
     detail::pythonbuf buffer;
+    bool active = true;
 
 public:
     explicit scoped_ostream_redirect(std::ostream &costream = std::cout,
@@ -170,10 +194,22 @@
         old = costream.rdbuf(&buffer);
     }
 
-    ~scoped_ostream_redirect() { costream.rdbuf(old); }
+    ~scoped_ostream_redirect() {
+        if (active) {
+            costream.rdbuf(old);
+        }
+    }
 
     scoped_ostream_redirect(const scoped_ostream_redirect &) = delete;
-    scoped_ostream_redirect(scoped_ostream_redirect &&other) = default;
+    // NOLINTNEXTLINE(performance-noexcept-move-constructor)
+    scoped_ostream_redirect(scoped_ostream_redirect &&other)
+        : old(other.old), costream(other.costream), buffer(std::move(other.buffer)),
+          active(other.active) {
+        if (active) {
+            costream.rdbuf(&buffer); // Re-point stream to our buffer
+            other.active = false;
+        }
+    }
     scoped_ostream_redirect &operator=(const scoped_ostream_redirect &) = delete;
     scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete;
 };
diff -Nru pybind11-3.0.1/include/pybind11/native_enum.h pybind11-3.0.4/include/pybind11/native_enum.h
--- pybind11-3.0.1/include/pybind11/native_enum.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/native_enum.h	2026-04-19 06:01:51.000000000 +0300
@@ -22,12 +22,12 @@
 public:
     using Underlying = typename std::underlying_type<EnumType>::type;
 
-    native_enum(const object &parent_scope,
+    native_enum(handle parent_scope,
                 const char *name,
                 const char *native_type_name,
                 const char *class_doc = "")
         : detail::native_enum_data(
-              parent_scope, name, native_type_name, class_doc, std::type_index(typeid(EnumType))) {
+              parent_scope, name, native_type_name, class_doc, make_record()) {
         if (detail::get_local_type_info(typeid(EnumType)) != nullptr
             || detail::get_global_type_info(typeid(EnumType)) != nullptr) {
             pybind11_fail(
@@ -62,6 +62,15 @@
 
     native_enum(const native_enum &) = delete;
     native_enum &operator=(const native_enum &) = delete;
+
+private:
+    static detail::native_enum_record make_record() {
+        detail::native_enum_record ret;
+        ret.cpptype = &typeid(EnumType);
+        ret.size_bytes = sizeof(EnumType);
+        ret.is_signed = std::is_signed<Underlying>::value;
+        return ret;
+    }
 };
 
 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/numpy.h pybind11-3.0.4/include/pybind11/numpy.h
--- pybind11-3.0.1/include/pybind11/numpy.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/numpy.h	2026-04-19 06:01:51.000000000 +0300
@@ -29,6 +29,10 @@
 #include <utility>
 #include <vector>
 
+#ifdef PYBIND11_HAS_SPAN
+#    include <span>
+#endif
+
 #if defined(PYBIND11_NUMPY_1_ONLY)
 #    error "PYBIND11_NUMPY_1_ONLY is no longer supported (see PR #5595)."
 #endif
@@ -416,7 +420,7 @@
                                  || std::is_same<typename T::value_type, const double>::value
                                         > (const_name("numpy.complex")
                                                + const_name<sizeof(typename T::value_type) * 16>(),
-                                           const_name("numpy.longcomplex"));
+                                           const_name("numpy.clongdouble"));
 };
 
 template <typename T>
@@ -1143,6 +1147,13 @@
     /// Dimensions of the array
     const ssize_t *shape() const { return detail::array_proxy(m_ptr)->dimensions; }
 
+#ifdef PYBIND11_HAS_SPAN
+    /// Dimensions of the array as a span
+    std::span<const ssize_t, std::dynamic_extent> shape_span() const {
+        return std::span(shape(), static_cast<std::size_t>(ndim()));
+    }
+#endif
+
     /// Dimension along a given axis
     ssize_t shape(ssize_t dim) const {
         if (dim >= ndim()) {
@@ -1154,6 +1165,13 @@
     /// Strides of the array
     const ssize_t *strides() const { return detail::array_proxy(m_ptr)->strides; }
 
+#ifdef PYBIND11_HAS_SPAN
+    /// Strides of the array as a span
+    std::span<const ssize_t, std::dynamic_extent> strides_span() const {
+        return std::span(strides(), static_cast<std::size_t>(ndim()));
+    }
+#endif
+
     /// Stride along a given axis
     ssize_t strides(ssize_t dim) const {
         if (dim >= ndim()) {
@@ -1860,7 +1878,7 @@
     using value_type = container_type::value_type;
     using size_type = container_type::size_type;
 
-    common_iterator() : m_strides() {}
+    common_iterator() = default;
 
     common_iterator(void *ptr, const container_type &strides, const container_type &shape)
         : p_ptr(reinterpret_cast<char *>(ptr)), m_strides(strides.size()) {
@@ -2309,4 +2327,86 @@
     return Helper(std::mem_fn(f));
 }
 
+// Intentionally no &&/const&& overloads: vectorized method calls operate on the bound Python
+// instance and should not consume/move-from self.
+// Vectorize a class method (non-const, lvalue ref-qualified):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) &>())),
+              Return,
+              Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) &) {
+    return Helper(std::mem_fn(f));
+}
+
+// Vectorize a class method (const, lvalue ref-qualified):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const &>())),
+              Return,
+              const Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) const &) {
+    return Helper(std::mem_fn(f));
+}
+
+#ifdef __cpp_noexcept_function_type
+// Vectorize a class method (non-const, noexcept):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
+              Return,
+              Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) noexcept) {
+    return Helper(std::mem_fn(f));
+}
+
+// Vectorize a class method (const, noexcept):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
+              Return,
+              const Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
+    return Helper(std::mem_fn(f));
+}
+
+// Vectorize a class method (non-const, lvalue ref-qualified, noexcept):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) & noexcept>())),
+              Return,
+              Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) & noexcept) {
+    return Helper(std::mem_fn(f));
+}
+
+// Vectorize a class method (const, lvalue ref-qualified, noexcept):
+template <typename Return,
+          typename Class,
+          typename... Args,
+          typename Helper = detail::vectorize_helper<
+              decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const & noexcept>())),
+              Return,
+              const Class *,
+              Args...>>
+Helper vectorize(Return (Class::*f)(Args...) const & noexcept) {
+    return Helper(std::mem_fn(f));
+}
+#endif
+
 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/pybind11.h pybind11-3.0.4/include/pybind11/pybind11.h
--- pybind11-3.0.1/include/pybind11/pybind11.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/pybind11.h	2026-04-19 06:01:51.000000000 +0300
@@ -43,6 +43,12 @@
 #    include <cxxabi.h>
 #endif
 
+#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606
+#    define PYBIND11_MAYBE_CONSTEXPR constexpr
+#else
+#    define PYBIND11_MAYBE_CONSTEXPR
+#endif
+
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 
 /* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
@@ -120,7 +126,8 @@
         const auto c = *pc;
         if (c == '{') {
             // Write arg name for everything except *args and **kwargs.
-            is_starred = *(pc + 1) == '*';
+            // Detect {@*args...} or {@**kwargs...}
+            is_starred = *(pc + 1) == '@' && *(pc + 2) == '*';
             if (is_starred) {
                 continue;
             }
@@ -158,7 +165,7 @@
                 pybind11_fail("Internal error while parsing type signature (1)");
             }
             if (auto *tinfo = detail::get_type_info(*t)) {
-                handle th((PyObject *) tinfo->type);
+                handle th(reinterpret_cast<PyObject *>(tinfo->type));
                 signature += th.attr("__module__").cast<std::string>() + "."
                              + th.attr("__qualname__").cast<std::string>();
             } else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
@@ -248,6 +255,49 @@
 #    define PYBIND11_COMPAT_STRDUP strdup
 #endif
 
+#define PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR                                                 \
+    detail::const_name("(") + cast_in::arg_names + detail::const_name(") -> ") + cast_out::name
+
+// We factor out readable function signatures to a specific template
+// so that they don't get duplicated across different instantiations of
+// cpp_function::initialize (which is templated on more types).
+template <typename cast_in, typename cast_out>
+class ReadableFunctionSignature {
+public:
+    using sig_type = decltype(PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR);
+
+private:
+    // We have to repeat PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR in decltype()
+    // because C++11 doesn't allow functions to return `auto`. (We don't
+    // know the type because it's some variant of detail::descr<N> with
+    // unknown N.)
+    static constexpr sig_type sig() { return PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR; }
+
+public:
+    static constexpr sig_type kSig = sig();
+    // We can only stash the result of detail::descr::types() in a
+    // constexpr variable if we aren't on MSVC (see
+    // PYBIND11_DESCR_CONSTEXPR).
+#if !defined(_MSC_VER)
+    using types_type = decltype(sig_type::types());
+    static constexpr types_type kTypes = sig_type::types();
+#endif
+};
+#undef PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR
+
+// Prior to C++17, we don't have inline variables, so we have to
+// provide an out-of-line definition of the class member.
+#if !defined(PYBIND11_CPP17)
+template <typename cast_in, typename cast_out>
+constexpr typename ReadableFunctionSignature<cast_in, cast_out>::sig_type
+    ReadableFunctionSignature<cast_in, cast_out>::kSig;
+#    if !defined(_MSC_VER)
+template <typename cast_in, typename cast_out>
+constexpr typename ReadableFunctionSignature<cast_in, cast_out>::types_type
+    ReadableFunctionSignature<cast_in, cast_out>::kTypes;
+#    endif
+#endif
+
 PYBIND11_NAMESPACE_END(detail)
 
 /// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object
@@ -319,6 +369,96 @@
                    extra...);
     }
 
+    /// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) &&, const Extra &...extra) {
+        initialize(
+            [f](Class *c, Arg... args) -> Return {
+                return (std::move(*c).*f)(std::forward<Arg>(args)...);
+            },
+            (Return (*)(Class *, Arg...)) nullptr,
+            extra...);
+    }
+
+    /// Construct a cpp_function from a class method (const, rvalue ref-qualifier)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) const &&, const Extra &...extra) {
+        initialize(
+            [f](const Class *c, Arg... args) -> Return {
+                return (std::move(*c).*f)(std::forward<Arg>(args)...);
+            },
+            (Return (*)(const Class *, Arg...)) nullptr,
+            extra...);
+    }
+
+#ifdef __cpp_noexcept_function_type
+    /// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) noexcept, const Extra &...extra) {
+        initialize(
+            [f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
+            (Return (*)(Class *, Arg...)) nullptr,
+            extra...);
+    }
+
+    /// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) & noexcept, const Extra &...extra) {
+        initialize(
+            [f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
+            (Return (*)(Class *, Arg...)) nullptr,
+            extra...);
+    }
+
+    /// Construct a cpp_function from a class method (const, no ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) const noexcept, const Extra &...extra) {
+        initialize([f](const Class *c,
+                       Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
+                   (Return (*)(const Class *, Arg...)) nullptr,
+                   extra...);
+    }
+
+    /// Construct a cpp_function from a class method (const, lvalue ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) const & noexcept, const Extra &...extra) {
+        initialize([f](const Class *c,
+                       Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
+                   (Return (*)(const Class *, Arg...)) nullptr,
+                   extra...);
+    }
+
+    /// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) && noexcept, const Extra &...extra) {
+        initialize(
+            [f](Class *c, Arg... args) -> Return {
+                return (std::move(*c).*f)(std::forward<Arg>(args)...);
+            },
+            (Return (*)(Class *, Arg...)) nullptr,
+            extra...);
+    }
+
+    /// Construct a cpp_function from a class method (const, rvalue ref-qualifier, noexcept)
+    template <typename Return, typename Class, typename... Arg, typename... Extra>
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    cpp_function(Return (Class::*f)(Arg...) const && noexcept, const Extra &...extra) {
+        initialize(
+            [f](const Class *c, Arg... args) -> Return {
+                return (std::move(*c).*f)(std::forward<Arg>(args)...);
+            },
+            (Return (*)(const Class *, Arg...)) nullptr,
+            extra...);
+    }
+#endif
+
     /// Return the function name
     object name() const { return attr("__name__"); }
 
@@ -481,9 +621,14 @@
 
         /* Generate a readable signature describing the function's arguments and return
            value types */
-        static constexpr auto signature
-            = const_name("(") + cast_in::arg_names + const_name(") -> ") + cast_out::name;
-        PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types();
+        static constexpr const auto &signature
+            = detail::ReadableFunctionSignature<cast_in, cast_out>::kSig;
+#if !defined(_MSC_VER)
+        static constexpr const auto &types
+            = detail::ReadableFunctionSignature<cast_in, cast_out>::kTypes;
+#else
+        PYBIND11_DESCR_CONSTEXPR auto types = std::decay<decltype(signature)>::type::types();
+#endif
 
         /* Register the function with Python from generic (non-templated) code */
         // Pass on the ownership over the `unique_rec` to `initialize_generic`. `rec` stays valid.
@@ -593,7 +738,7 @@
 
         rec->signature = guarded_strdup(signature.c_str());
         rec->args.shrink_to_fit();
-        rec->nargs = (std::uint16_t) args;
+        rec->nargs = static_cast<std::uint16_t>(args);
 
         if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) {
             rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr());
@@ -629,10 +774,10 @@
             rec->def->ml_name = rec->name;
             rec->def->ml_meth
                 = reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(dispatcher));
-            rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
+            rec->def->ml_flags = METH_FASTCALL | METH_KEYWORDS;
 
             object py_func_rec = detail::function_record_PyObject_New();
-            ((detail::function_record_PyObject *) py_func_rec.ptr())->cpp_func_rec
+            (reinterpret_cast<detail::function_record_PyObject *>(py_func_rec.ptr()))->cpp_func_rec
                 = unique_rec.release();
             guarded_strdup.release();
 
@@ -666,8 +811,8 @@
                 // chain.
                 chain_start = rec;
                 rec->next = chain;
-                auto *py_func_rec
-                    = (detail::function_record_PyObject *) PyCFunction_GET_SELF(m_ptr);
+                auto *py_func_rec = reinterpret_cast<detail::function_record_PyObject *>(
+                    PyCFunction_GET_SELF(m_ptr));
                 py_func_rec->cpp_func_rec = unique_rec.release();
                 guarded_strdup.release();
             } else {
@@ -727,7 +872,7 @@
             }
         }
 
-        auto *func = (PyCFunctionObject *) m_ptr;
+        auto *func = reinterpret_cast<PyCFunctionObject *>(m_ptr);
         // Install docstring if it's non-empty (when at least one option is enabled)
         auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
         std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
@@ -762,9 +907,9 @@
             // so they cannot be freed. Once the function has been created, they can.
             // Check `make_function_record` for more details.
             if (free_strings) {
-                std::free((char *) rec->name);
-                std::free((char *) rec->doc);
-                std::free((char *) rec->signature);
+                std::free(rec->name);
+                std::free(rec->doc);
+                std::free(rec->signature);
                 for (auto &arg : rec->args) {
                     std::free(const_cast<char *>(arg.name));
                     std::free(const_cast<char *>(arg.descr));
@@ -792,7 +937,8 @@
     }
 
     /// Main dispatch logic for calls to functions bound using pybind11
-    static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
+    static PyObject *
+    dispatcher(PyObject *self, PyObject *const *args_in_arr, size_t nargsf, PyObject *kwnames_in) {
         using namespace detail;
         const function_record *overloads = function_record_ptr_from_PyObject(self);
         assert(overloads != nullptr);
@@ -802,9 +948,9 @@
 
         /* Need to know how many arguments + keyword arguments there are to pick the right
            overload */
-        const auto n_args_in = (size_t) PyTuple_GET_SIZE(args_in);
+        const auto n_args_in = static_cast<size_t>(PyVectorcall_NARGS(nargsf));
 
-        handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr,
+        handle parent = n_args_in > 0 ? args_in_arr[0] : nullptr,
                result = PYBIND11_TRY_NEXT_OVERLOAD;
 
         auto self_value_and_holder = value_and_holder();
@@ -816,7 +962,8 @@
                 return nullptr;
             }
 
-            auto *const tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
+            auto *const tinfo
+                = get_type_info(reinterpret_cast<PyTypeObject *>(overloads->scope.ptr()));
             auto *const pi = reinterpret_cast<instance *>(parent.ptr());
             self_value_and_holder = pi->get_value_and_holder(tinfo, true);
 
@@ -892,7 +1039,7 @@
                         self_value_and_holder.type->dealloc(self_value_and_holder);
                     }
 
-                    call.init_self = PyTuple_GET_ITEM(args_in, 0);
+                    call.init_self = args_in_arr[0];
                     call.args.emplace_back(reinterpret_cast<PyObject *>(&self_value_and_holder));
                     call.args_convert.push_back(false);
                     ++args_copied;
@@ -903,17 +1050,24 @@
                 for (; args_copied < args_to_copy; ++args_copied) {
                     const argument_record *arg_rec
                         = args_copied < func.args.size() ? &func.args[args_copied] : nullptr;
-                    if (kwargs_in && arg_rec && arg_rec->name
-                        && dict_getitemstring(kwargs_in, arg_rec->name)) {
+
+                    /* if the argument is listed in the call site's kwargs, but the argument is
+                    also fulfilled positionally, then the call can't match this overload. for
+                    example, the call site is: foo(0, key=1) but our overload is foo(key:int) then
+                    this call can't be for us, because it would be invalid.
+                    */
+                    if (kwnames_in && arg_rec && arg_rec->name
+                        && keyword_index(kwnames_in, arg_rec->name) >= 0) {
                         bad_arg = true;
                         break;
                     }
 
-                    handle arg(PyTuple_GET_ITEM(args_in, args_copied));
+                    handle arg(args_in_arr[args_copied]);
                     if (arg_rec && !arg_rec->none && arg.is_none()) {
                         bad_arg = true;
                         break;
                     }
+
                     call.args.push_back(arg);
                     call.args_convert.push_back(arg_rec ? arg_rec->convert : true);
                 }
@@ -925,20 +1079,12 @@
                 // to copy the rest into a py::args argument.
                 size_t positional_args_copied = args_copied;
 
-                // We'll need to copy this if we steal some kwargs for defaults
-                dict kwargs = reinterpret_borrow<dict>(kwargs_in);
-
                 // 1.5. Fill in any missing pos_only args from defaults if they exist
                 if (args_copied < func.nargs_pos_only) {
                     for (; args_copied < func.nargs_pos_only; ++args_copied) {
                         const auto &arg_rec = func.args[args_copied];
-                        handle value;
-
                         if (arg_rec.value) {
-                            value = arg_rec.value;
-                        }
-                        if (value) {
-                            call.args.push_back(value);
+                            call.args.push_back(arg_rec.value);
                             call.args_convert.push_back(arg_rec.convert);
                         } else {
                             break;
@@ -951,46 +1097,42 @@
                 }
 
                 // 2. Check kwargs and, failing that, defaults that may help complete the list
+                small_vector<bool, arg_vector_small_size> used_kwargs(
+                    kwnames_in ? static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)) : 0, false);
+                size_t used_kwargs_count = 0;
                 if (args_copied < num_args) {
-                    bool copied_kwargs = false;
-
                     for (; args_copied < num_args; ++args_copied) {
                         const auto &arg_rec = func.args[args_copied];
 
                         handle value;
-                        if (kwargs_in && arg_rec.name) {
-                            value = dict_getitemstring(kwargs.ptr(), arg_rec.name);
+                        if (kwnames_in && arg_rec.name) {
+                            ssize_t i = keyword_index(kwnames_in, arg_rec.name);
+                            if (i >= 0) {
+                                value = args_in_arr[n_args_in + static_cast<size_t>(i)];
+                                used_kwargs.set(static_cast<size_t>(i), true);
+                                used_kwargs_count++;
+                            }
                         }
 
-                        if (value) {
-                            // Consume a kwargs value
-                            if (!copied_kwargs) {
-                                kwargs = reinterpret_steal<dict>(PyDict_Copy(kwargs.ptr()));
-                                copied_kwargs = true;
-                            }
-                            if (PyDict_DelItemString(kwargs.ptr(), arg_rec.name) == -1) {
-                                throw error_already_set();
-                            }
-                        } else if (arg_rec.value) {
+                        if (!value) {
                             value = arg_rec.value;
+                            if (!value) {
+                                break;
+                            }
                         }
 
                         if (!arg_rec.none && value.is_none()) {
                             break;
                         }
 
-                        if (value) {
-                            // If we're at the py::args index then first insert a stub for it to be
-                            // replaced later
-                            if (func.has_args && call.args.size() == func.nargs_pos) {
-                                call.args.push_back(none());
-                            }
-
-                            call.args.push_back(value);
-                            call.args_convert.push_back(arg_rec.convert);
-                        } else {
-                            break;
+                        // If we're at the py::args index then first insert a stub for it to be
+                        // replaced later
+                        if (func.has_args && call.args.size() == func.nargs_pos) {
+                            call.args.push_back(none());
                         }
+
+                        call.args.push_back(value);
+                        call.args_convert.push_back(arg_rec.convert);
                     }
 
                     if (args_copied < num_args) {
@@ -1000,47 +1142,50 @@
                 }
 
                 // 3. Check everything was consumed (unless we have a kwargs arg)
-                if (kwargs && !kwargs.empty() && !func.has_kwargs) {
+                if (!func.has_kwargs && used_kwargs_count < used_kwargs.size()) {
                     continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
                 }
 
                 // 4a. If we have a py::args argument, create a new tuple with leftovers
                 if (func.has_args) {
-                    tuple extra_args;
-                    if (args_to_copy == 0) {
-                        // We didn't copy out any position arguments from the args_in tuple, so we
-                        // can reuse it directly without copying:
-                        extra_args = reinterpret_borrow<tuple>(args_in);
-                    } else if (positional_args_copied >= n_args_in) {
-                        extra_args = tuple(0);
+                    if (positional_args_copied >= n_args_in) {
+                        call.args_ref = tuple(0);
                     } else {
                         size_t args_size = n_args_in - positional_args_copied;
-                        extra_args = tuple(args_size);
+                        tuple extra_args(args_size);
                         for (size_t i = 0; i < args_size; ++i) {
-                            extra_args[i] = PyTuple_GET_ITEM(args_in, positional_args_copied + i);
+                            extra_args[i] = args_in_arr[positional_args_copied + i];
                         }
+                        call.args_ref = std::move(extra_args);
                     }
                     if (call.args.size() <= func.nargs_pos) {
-                        call.args.push_back(extra_args);
+                        call.args.push_back(call.args_ref);
                     } else {
-                        call.args[func.nargs_pos] = extra_args;
+                        call.args[func.nargs_pos] = call.args_ref;
                     }
                     call.args_convert.push_back(false);
-                    call.args_ref = std::move(extra_args);
                 }
 
                 // 4b. If we have a py::kwargs, pass on any remaining kwargs
                 if (func.has_kwargs) {
-                    if (!kwargs.ptr()) {
-                        kwargs = dict(); // If we didn't get one, send an empty one
+                    dict kwargs;
+                    for (size_t i = 0; i < used_kwargs.size(); ++i) {
+                        if (!used_kwargs[i]) {
+                            // Cast values into handles before indexing into kwargs to ensure
+                            // well-defined evaluation order (MSVC C4866).
+                            handle arg_in_arr = args_in_arr[n_args_in + i],
+                                   kwname = PyTuple_GET_ITEM(kwnames_in, i);
+                            kwargs[kwname] = arg_in_arr;
+                        }
                     }
                     call.args.push_back(kwargs);
                     call.args_convert.push_back(false);
                     call.kwargs_ref = std::move(kwargs);
                 }
 
-// 5. Put everything in a vector.  Not technically step 5, we've been building it
-// in `call.args` all along.
+                // 5. Put everything in a vector.  Not technically step 5, we've been building it
+                // in `call.args` all along.
+
 #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
                 if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) {
                     pybind11_fail("Internal error: function call dispatcher inserted wrong number "
@@ -1048,13 +1193,14 @@
                 }
 #endif
 
-                std::vector<bool> second_pass_convert;
+                args_convert_vector<arg_vector_small_size> second_pass_convert;
                 if (overloaded) {
                     // We're in the first no-convert pass, so swap out the conversion flags for a
                     // set of all-false flags.  If the call fails, we'll swap the flags back in for
                     // the conversion-allowed call below.
-                    second_pass_convert.resize(func.nargs, false);
-                    call.args_convert.swap(second_pass_convert);
+                    second_pass_convert = std::move(call.args_convert);
+                    call.args_convert
+                        = args_convert_vector<arg_vector_small_size>(func.nargs, false);
                 }
 
                 // 6. Call the function.
@@ -1170,40 +1316,37 @@
                 msg += '\n';
             }
             msg += "\nInvoked with: ";
-            auto args_ = reinterpret_borrow<tuple>(args_in);
             bool some_args = false;
-            for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
+            for (size_t ti = overloads->is_constructor ? 1 : 0; ti < n_args_in; ++ti) {
                 if (!some_args) {
                     some_args = true;
                 } else {
                     msg += ", ";
                 }
                 try {
-                    msg += pybind11::repr(args_[ti]);
+                    msg += pybind11::repr(args_in_arr[ti]);
                 } catch (const error_already_set &) {
                     msg += "<repr raised Error>";
                 }
             }
-            if (kwargs_in) {
-                auto kwargs = reinterpret_borrow<dict>(kwargs_in);
-                if (!kwargs.empty()) {
-                    if (some_args) {
-                        msg += "; ";
+            if (kwnames_in && PyTuple_GET_SIZE(kwnames_in) > 0) {
+                if (some_args) {
+                    msg += "; ";
+                }
+                msg += "kwargs: ";
+                bool first = true;
+                for (size_t i = 0; i < static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)); ++i) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        msg += ", ";
                     }
-                    msg += "kwargs: ";
-                    bool first = true;
-                    for (const auto &kwarg : kwargs) {
-                        if (first) {
-                            first = false;
-                        } else {
-                            msg += ", ";
-                        }
-                        msg += pybind11::str("{}=").format(kwarg.first);
-                        try {
-                            msg += pybind11::repr(kwarg.second);
-                        } catch (const error_already_set &) {
-                            msg += "<repr raised Error>";
-                        }
+                    msg += reinterpret_borrow<pybind11::str>(PyTuple_GET_ITEM(kwnames_in, i));
+                    msg += '=';
+                    try {
+                        msg += pybind11::repr(args_in_arr[n_args_in + i]);
+                    } catch (const error_already_set &) {
+                        msg += "<repr raised Error>";
                     }
                 }
             }
@@ -1238,6 +1381,28 @@
         }
         return result.ptr();
     }
+
+    static ssize_t keyword_index(PyObject *haystack, char const *needle) {
+        /* kwargs is usually very small (<= 5 entries).  The arg strings are typically interned.
+         * CPython itself implements the search this way, first comparing all pointers ... which is
+         * cheap and will work if the strings are interned.  If it fails, then it falls back to a
+         * second lexicographic check. This is wildly expensive for huge argument lists, but those
+         * are incredibly rare so we optimize for the vastly common case of just a couple of args.
+         */
+        auto n = PyTuple_GET_SIZE(haystack);
+        auto s = reinterpret_steal<pybind11::str>(PyUnicode_InternFromString(needle));
+        for (ssize_t i = 0; i < n; ++i) {
+            if (PyTuple_GET_ITEM(haystack, i) == s.ptr()) {
+                return i;
+            }
+        }
+        for (ssize_t i = 0; i < n; ++i) {
+            if (PyUnicode_Compare(PyTuple_GET_ITEM(haystack, i), s.ptr()) == 0) {
+                return i;
+            }
+        }
+        return -1;
+    }
 };
 
 PYBIND11_NAMESPACE_BEGIN(detail)
@@ -1246,9 +1411,15 @@
 
 // This implementation needs the definition of `class cpp_function`.
 inline void tp_dealloc_impl(PyObject *self) {
-    auto *py_func_rec = (function_record_PyObject *) self;
+    // Save type before PyObject_Free invalidates self.
+    auto *type = Py_TYPE(self);
+    auto *py_func_rec = reinterpret_cast<function_record_PyObject *>(self);
     cpp_function::destruct(py_func_rec->cpp_func_rec);
     py_func_rec->cpp_func_rec = nullptr;
+    // PyObject_New increments the heap type refcount and allocates via
+    // PyObject_Malloc; balance both here
+    PyObject_Free(self);
+    Py_DECREF(type);
 }
 
 PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)
@@ -1619,7 +1790,7 @@
 
         /* Register supplemental type information in C++ dict */
         auto *tinfo = new detail::type_info();
-        tinfo->type = (PyTypeObject *) m_ptr;
+        tinfo->type = reinterpret_cast<PyTypeObject *>(m_ptr);
         tinfo->cpptype = rec.type;
         tinfo->type_size = rec.type_size;
         tinfo->type_align = rec.type_align;
@@ -1636,10 +1807,14 @@
         with_internals([&](internals &internals) {
             auto tindex = std::type_index(*rec.type);
             tinfo->direct_conversions = &internals.direct_conversions[tindex];
+            auto &local_internals = get_local_internals();
             if (rec.module_local) {
-                get_local_internals().registered_types_cpp[tindex] = tinfo;
+                local_internals.registered_types_cpp[rec.type] = tinfo;
             } else {
                 internals.registered_types_cpp[tindex] = tinfo;
+#if PYBIND11_INTERNALS_VERSION >= 12
+                internals.registered_types_cpp_fast[rec.type] = tinfo;
+#endif
             }
 
             PYBIND11_WARNING_PUSH
@@ -1650,7 +1825,7 @@
             PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds")
             PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread")
 #endif
-            internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo};
+            internals.registered_types_py[reinterpret_cast<PyTypeObject *>(m_ptr)] = {tinfo};
             PYBIND11_WARNING_POP
         });
 
@@ -1658,7 +1833,8 @@
             mark_parents_nonsimple(tinfo->type);
             tinfo->simple_ancestors = false;
         } else if (rec.bases.size() == 1) {
-            auto *parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr());
+            auto *parent_tinfo
+                = get_type_info(reinterpret_cast<PyTypeObject *>(rec.bases[0].ptr()));
             assert(parent_tinfo != nullptr);
             bool parent_simple_ancestors = parent_tinfo->simple_ancestors;
             tinfo->simple_ancestors = parent_simple_ancestors;
@@ -1677,17 +1853,17 @@
     void mark_parents_nonsimple(PyTypeObject *value) {
         auto t = reinterpret_borrow<tuple>(value->tp_bases);
         for (handle h : t) {
-            auto *tinfo2 = get_type_info((PyTypeObject *) h.ptr());
+            auto *tinfo2 = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()));
             if (tinfo2) {
                 tinfo2->simple_type = false;
             }
-            mark_parents_nonsimple((PyTypeObject *) h.ptr());
+            mark_parents_nonsimple(reinterpret_cast<PyTypeObject *>(h.ptr()));
         }
     }
 
     void install_buffer_funcs(buffer_info *(*get_buffer)(PyObject *, void *),
                               void *get_buffer_data) {
-        auto *type = (PyHeapTypeObject *) m_ptr;
+        auto *type = reinterpret_cast<PyHeapTypeObject *>(m_ptr);
         auto *tinfo = detail::get_type_info(&type->ht_type);
 
         if (!type->ht_type.tp_as_buffer) {
@@ -1709,8 +1885,8 @@
         const auto is_static = (rec_func != nullptr) && !(rec_func->is_method && rec_func->scope);
         const auto has_doc = (rec_func != nullptr) && (rec_func->doc != nullptr)
                              && pybind11::options::show_user_defined_docstrings();
-        auto property = handle(
-            (PyObject *) (is_static ? get_internals().static_property_type : &PyProperty_Type));
+        auto property = handle(reinterpret_cast<PyObject *>(
+            is_static ? get_internals().static_property_type : &PyProperty_Type));
         attr(name) = property(fget.ptr() ? fget : none(),
                               fset.ptr() ? fset : none(),
                               /*deleter*/ none(),
@@ -1779,29 +1955,89 @@
     }
 }
 
-PYBIND11_NAMESPACE_END(detail)
-
-/// Given a pointer to a member function, cast it to its `Derived` version.
-/// Forward everything else unchanged.
-template <typename /*Derived*/, typename F>
-auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
-    return std::forward<F>(f);
-}
-
+/// Type trait to rebind a member function pointer's class to `Derived`, preserving all
+/// cv/ref/noexcept qualifiers. The primary template has no `type` member, providing SFINAE
+/// failure for unsupported member function pointer types. `source_class` holds the original
+/// class for use in `is_accessible_base_of` checks.
+template <typename Derived, typename T>
+struct rebind_member_ptr {};
+
+// Define one specialization per supported qualifier combination via a local macro.
+// The qualifiers argument appears in type position, not expression position, so
+// parenthesizing it would produce invalid C++.
+// The no-qualifier specialization is written out explicitly to avoid invoking the macro with an
+// empty argument, which triggers MSVC warning C4003.
 template <typename Derived, typename Return, typename Class, typename... Args>
-auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(Args...) {
+struct rebind_member_ptr<Derived, Return (Class::*)(Args...)> {
+    using type = Return (Derived::*)(Args...);
+    using source_class = Class;
+};
+// NOLINTBEGIN(bugprone-macro-parentheses)
+#define PYBIND11_REBIND_MEMBER_PTR(qualifiers)                                                    \
+    template <typename Derived, typename Return, typename Class, typename... Args>                \
+    struct rebind_member_ptr<Derived, Return (Class::*)(Args...) qualifiers> {                    \
+        using type = Return (Derived::*)(Args...) qualifiers;                                     \
+        using source_class = Class;                                                               \
+    }
+PYBIND11_REBIND_MEMBER_PTR(const);
+PYBIND11_REBIND_MEMBER_PTR(&);
+PYBIND11_REBIND_MEMBER_PTR(const &);
+PYBIND11_REBIND_MEMBER_PTR(&&);
+PYBIND11_REBIND_MEMBER_PTR(const &&);
+#ifdef __cpp_noexcept_function_type
+PYBIND11_REBIND_MEMBER_PTR(noexcept);
+PYBIND11_REBIND_MEMBER_PTR(const noexcept);
+PYBIND11_REBIND_MEMBER_PTR(& noexcept);
+PYBIND11_REBIND_MEMBER_PTR(const & noexcept);
+PYBIND11_REBIND_MEMBER_PTR(&& noexcept);
+PYBIND11_REBIND_MEMBER_PTR(const && noexcept);
+#endif
+#undef PYBIND11_REBIND_MEMBER_PTR
+// NOLINTEND(bugprone-macro-parentheses)
+
+/// Shared implementation body for all method_adaptor member-function-pointer overloads.
+/// Asserts Base is accessible from Derived, then casts the member pointer.
+template <typename Derived,
+          typename T,
+          typename Traits = rebind_member_ptr<Derived, T>,
+          typename Adapted = typename Traits::type>
+constexpr PYBIND11_ALWAYS_INLINE Adapted adapt_member_ptr(T pmf) {
     static_assert(
-        detail::is_accessible_base_of<Class, Derived>::value,
+        detail::is_accessible_base_of<typename Traits::source_class, Derived>::value,
         "Cannot bind an inaccessible base class method; use a lambda definition instead");
     return pmf;
 }
 
-template <typename Derived, typename Return, typename Class, typename... Args>
-auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(Args...) const {
-    static_assert(
-        detail::is_accessible_base_of<Class, Derived>::value,
-        "Cannot bind an inaccessible base class method; use a lambda definition instead");
-    return pmf;
+PYBIND11_NAMESPACE_END(detail)
+
+/// Given a pointer to a member function, cast it to its `Derived` version.
+/// For all other callables (lambdas, function pointers, etc.), forward unchanged.
+///
+/// Two overloads cover all cases without explicit per-qualifier instantiations:
+///
+///  (1) Generic fallback — disabled for member function pointers so that (2) wins
+///      without any partial-ordering ambiguity.
+///  (2) MFP overload — SFINAE on rebind_member_ptr::type, which exists for every
+///      supported qualifier combination (const, &, &&, noexcept, ...).  A single
+///      template therefore covers all combinations that rebind_member_ptr handles.
+template <
+    typename /*Derived*/,
+    typename F,
+    detail::enable_if_t<!std::is_member_function_pointer<detail::remove_reference_t<F>>::value,
+                        int> = 0>
+constexpr auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
+    return std::forward<F>(f);
+}
+
+template <typename Derived,
+          typename T,
+          typename Adapted = typename detail::rebind_member_ptr<Derived, T>::type>
+constexpr Adapted method_adaptor(T pmf) {
+    // Expected to be redundant (SFINAE on rebind_member_ptr) but cheap and makes the intent
+    // explicit.
+    static_assert(std::is_member_function_pointer<T>::value,
+                  "method_adaptor: T must be a member function pointer");
+    return detail::adapt_member_ptr<Derived>(pmf);
 }
 
 PYBIND11_NAMESPACE_BEGIN(detail)
@@ -2137,10 +2373,18 @@
 
         if (has_alias) {
             with_internals([&](internals &internals) {
-                auto &instances = record.module_local ? get_local_internals().registered_types_cpp
-                                                      : internals.registered_types_cpp;
-                instances[std::type_index(typeid(type_alias))]
-                    = instances[std::type_index(typeid(type))];
+                auto &local_internals = get_local_internals();
+                if (record.module_local) {
+                    local_internals.registered_types_cpp[&typeid(type_alias)]
+                        = local_internals.registered_types_cpp[&typeid(type)];
+                } else {
+                    type_info *const val
+                        = internals.registered_types_cpp[std::type_index(typeid(type))];
+                    internals.registered_types_cpp[std::type_index(typeid(type_alias))] = val;
+#if PYBIND11_INTERNALS_VERSION >= 12
+                    internals.registered_types_cpp_fast[&typeid(type_alias)] = val;
+#endif
+                }
             });
         }
         def("_pybind11_conduit_v1_", cpp_conduit_method);
@@ -2151,6 +2395,13 @@
         rec.add_base(typeid(Base), [](void *src) -> void * {
             return static_cast<Base *>(reinterpret_cast<type *>(src));
         });
+        // Virtual inheritance means the base subobject is at a dynamic offset,
+        // so the reinterpret_cast shortcut in load_impl Case 2a is invalid.
+        // Force the MI path (implicit_casts) for correct pointer adjustment.
+        // Detection: static_cast<Derived*>(Base*) is ill-formed for virtual bases.
+        if PYBIND11_MAYBE_CONSTEXPR (!detail::is_static_downcastable<Base, type>::value) {
+            rec.multiple_inheritance = true;
+        }
     }
 
     template <typename Base, detail::enable_if_t<!is_base<Base>::value, int> = 0>
@@ -2252,6 +2503,40 @@
         return def_buffer([func](const type &obj) { return (obj.*func)(); });
     }
 
+    // Intentionally no &&/const&& overloads: buffer protocol callbacks are invoked on an
+    // existing Python object and should not move-from self.
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) &) {
+        return def_buffer([func](type &obj) { return (obj.*func)(); });
+    }
+
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) const &) {
+        return def_buffer([func](const type &obj) { return (obj.*func)(); });
+    }
+
+#ifdef __cpp_noexcept_function_type
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) noexcept) {
+        return def_buffer([func](type &obj) { return (obj.*func)(); });
+    }
+
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) const noexcept) {
+        return def_buffer([func](const type &obj) { return (obj.*func)(); });
+    }
+
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) & noexcept) {
+        return def_buffer([func](type &obj) { return (obj.*func)(); });
+    }
+
+    template <typename Return, typename Class, typename... Args>
+    class_ &def_buffer(Return (Class::*func)(Args...) const & noexcept) {
+        return def_buffer([func](const type &obj) { return (obj.*func)(); });
+    }
+#endif
+
     template <typename C, typename D, typename... Extra>
     class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
         static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
@@ -2368,24 +2653,52 @@
                                 const Extra &...extra) {
         static_assert(0 == detail::constexpr_sum(std::is_base_of<arg, Extra>::value...),
                       "Argument annotations are not allowed for properties");
+        static_assert(0 == detail::constexpr_sum(detail::is_call_guard<Extra>::value...),
+                      "def_property family does not currently support call_guard. Use a "
+                      "py::cpp_function instead.");
+        static_assert(0 == detail::constexpr_sum(detail::is_keep_alive<Extra>::value...),
+                      "def_property family does not currently support keep_alive. Use a "
+                      "py::cpp_function instead.");
         auto rec_fget = get_function_record(fget), rec_fset = get_function_record(fset);
         auto *rec_active = rec_fget;
         if (rec_fget) {
             char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific
                                                documentation string */
+            auto args_before = rec_fget->args.size();
             detail::process_attributes<Extra...>::init(extra..., rec_fget);
             if (rec_fget->doc && rec_fget->doc != doc_prev) {
                 std::free(doc_prev);
                 rec_fget->doc = PYBIND11_COMPAT_STRDUP(rec_fget->doc);
             }
+            // Args added by process_attributes (e.g. "self" via is_method + pos_only/kw_only)
+            // need their strings strdup'd: initialize_generic's strdup loop already ran during
+            // cpp_function construction, so it won't process these late additions. Without this,
+            // destruct() would call free() on string literals. See gh-5976.
+            for (auto i = args_before; i < rec_fget->args.size(); ++i) {
+                if (rec_fget->args[i].name) {
+                    rec_fget->args[i].name = PYBIND11_COMPAT_STRDUP(rec_fget->args[i].name);
+                }
+                if (rec_fget->args[i].descr) {
+                    rec_fget->args[i].descr = PYBIND11_COMPAT_STRDUP(rec_fget->args[i].descr);
+                }
+            }
         }
         if (rec_fset) {
             char *doc_prev = rec_fset->doc;
+            auto args_before = rec_fset->args.size();
             detail::process_attributes<Extra...>::init(extra..., rec_fset);
             if (rec_fset->doc && rec_fset->doc != doc_prev) {
                 std::free(doc_prev);
                 rec_fset->doc = PYBIND11_COMPAT_STRDUP(rec_fset->doc);
             }
+            for (auto i = args_before; i < rec_fset->args.size(); ++i) {
+                if (rec_fset->args[i].name) {
+                    rec_fset->args[i].name = PYBIND11_COMPAT_STRDUP(rec_fset->args[i].name);
+                }
+                if (rec_fset->args[i].descr) {
+                    rec_fset->args[i].descr = PYBIND11_COMPAT_STRDUP(rec_fset->args[i].descr);
+                }
+            }
             if (!rec_active) {
                 rec_active = rec_fset;
             }
@@ -2418,8 +2731,7 @@
     static void init_holder_from_existing(const detail::value_and_holder &v_h,
                                           const holder_type *holder_ptr,
                                           std::true_type /*is_copy_constructible*/) {
-        new (std::addressof(v_h.holder<holder_type>()))
-            holder_type(*reinterpret_cast<const holder_type *>(holder_ptr));
+        new (std::addressof(v_h.holder<holder_type>())) holder_type(*holder_ptr);
     }
 
     static void init_holder_from_existing(const detail::value_and_holder &v_h,
@@ -2631,8 +2943,9 @@
 
     PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) {
         m_base.attr("__entries") = dict();
-        auto property = handle((PyObject *) &PyProperty_Type);
-        auto static_property = handle((PyObject *) get_internals().static_property_type);
+        auto property = handle(reinterpret_cast<PyObject *>(&PyProperty_Type));
+        auto static_property
+            = handle(reinterpret_cast<PyObject *>(get_internals().static_property_type));
 
         m_base.attr("__repr__") = cpp_function(
             [](const object &arg) -> str {
@@ -2663,7 +2976,7 @@
                     [](handle arg) -> std::string {
                         std::string docstring;
                         dict entries = arg.attr("__entries");
-                        if (((PyTypeObject *) arg.ptr())->tp_doc) {
+                        if ((reinterpret_cast<PyTypeObject *>(arg.ptr()))->tp_doc) {
                             docstring += std::string(
                                 reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
                             docstring += "\n\n";
@@ -2789,7 +3102,7 @@
         dict entries = m_base.attr("__entries");
         str name(name_);
         if (entries.contains(name)) {
-            std::string type_name = (std::string) str(m_base.attr("__name__"));
+            std::string type_name = std::string(str(m_base.attr("__name__")));
             throw value_error(std::move(type_name) + ": element \"" + std::string(name_)
                               + "\" already exists!");
         }
@@ -2984,7 +3297,7 @@
     if (res.second) {
         // New cache entry created; set up a weak reference to automatically remove it if the type
         // gets destroyed:
-        weakref((PyObject *) type, cpp_function([type](handle wr) {
+        weakref(reinterpret_cast<PyObject *>(type), cpp_function([type](handle wr) {
                     with_internals([type](internals &internals) {
                         internals.registered_types_py.erase(type);
 
@@ -3079,6 +3392,11 @@
     using state = detail::iterator_state<Access, Policy, Iterator, Sentinel, ValueType, Extra...>;
     // TODO: state captures only the types of Extra, not the values
 
+    // For Python < 3.14.0rc1, pycritical_section uses direct mutex locking (same as a unique
+    // lock), which may deadlock during type registration. See detail/internals.h for details.
+#if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
+    PYBIND11_LOCK_INTERNALS(get_internals());
+#endif
     if (!detail::get_type_info(typeid(state), false)) {
         class_<state>(handle(), "iterator", pybind11::module_local())
             .def(
@@ -3200,13 +3518,10 @@
 
 template <typename InputType, typename OutputType>
 void implicitly_convertible() {
-    static int tss_sentinel_pointee = 1; // arbitrary value
     struct set_flag {
-        thread_specific_storage<int> &flag;
-        explicit set_flag(thread_specific_storage<int> &flag_) : flag(flag_) {
-            flag = &tss_sentinel_pointee; // trick: the pointer itself is the sentinel
-        }
-        ~set_flag() { flag.reset(nullptr); }
+        bool &flag;
+        explicit set_flag(bool &flag_) : flag(flag_) { flag_ = true; }
+        ~set_flag() { flag = false; }
 
         // Prevent copying/moving to ensure RAII guard is used safely
         set_flag(const set_flag &) = delete;
@@ -3215,7 +3530,7 @@
         set_flag &operator=(set_flag &&) = delete;
     };
     auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * {
-        static thread_specific_storage<int> currently_used;
+        thread_local bool currently_used = false;
         if (currently_used) { // implicit conversions are non-reentrant
             return nullptr;
         }
@@ -3225,7 +3540,7 @@
         }
         tuple args(1);
         args[0] = obj;
-        PyObject *result = PyObject_Call((PyObject *) type, args.ptr(), nullptr);
+        PyObject *result = PyObject_Call(reinterpret_cast<PyObject *>(type), args.ptr(), nullptr);
         if (result == nullptr) {
             PyErr_Clear();
         }
@@ -3441,7 +3756,7 @@
     if (frame != nullptr) {
         PyCodeObject *f_code = PyFrame_GetCode(frame);
         // f_code is guaranteed to not be NULL
-        if ((std::string) str(f_code->co_name) == name && f_code->co_argcount > 0) {
+        if (std::string(str(f_code->co_name)) == name && f_code->co_argcount > 0) {
 #        if PY_VERSION_HEX >= 0x030d0000
             PyObject *locals = PyEval_GetFrameLocals();
 #        else
@@ -3536,13 +3851,15 @@
             auto o = override(__VA_ARGS__);                                                       \
             PYBIND11_WARNING_PUSH                                                                 \
             PYBIND11_WARNING_DISABLE_MSVC(4127)                                                   \
-            if (pybind11::detail::cast_is_temporary_value_reference<ret_type>::value              \
+            if PYBIND11_MAYBE_CONSTEXPR (                                                         \
+                pybind11::detail::cast_is_temporary_value_reference<ret_type>::value              \
                 && !pybind11::detail::is_same_ignoring_cvref<ret_type, PyObject *>::value) {      \
                 static pybind11::detail::override_caster_t<ret_type> caster;                      \
                 return pybind11::detail::cast_ref<ret_type>(std::move(o), caster);                \
+            } else {                                                                              \
+                return pybind11::detail::cast_safe<ret_type>(std::move(o));                       \
             }                                                                                     \
             PYBIND11_WARNING_POP                                                                  \
-            return pybind11::detail::cast_safe<ret_type>(std::move(o));                           \
         }                                                                                         \
     } while (false)
 
diff -Nru pybind11-3.0.1/include/pybind11/pytypes.h pybind11-3.0.4/include/pybind11/pytypes.h
--- pybind11-3.0.1/include/pybind11/pytypes.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/pytypes.h	2026-04-19 06:01:51.000000000 +0300
@@ -259,8 +259,7 @@
         detail::enable_if_t<detail::all_of<detail::none_of<std::is_base_of<handle, T>,
                                                            detail::is_pyobj_ptr_or_nullptr_t<T>>,
                                            std::is_convertible<T, PyObject *>>::value,
-                            int>
-        = 0>
+                            int> = 0>
     // NOLINTNEXTLINE(google-explicit-constructor)
     handle(T &obj) : m_ptr(obj) {}
 
@@ -547,8 +546,13 @@
         // The presence of __notes__ is likely due to exception normalization
         // errors, although that is not necessarily true, therefore insert a
         // hint only:
-        if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) {
+        const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__");
+        if (has_notes == 1) {
             m_lazy_error_string += "[WITH __notes__]";
+        } else if (has_notes == -1) {
+            // Ignore secondary errors when probing for __notes__ to avoid leaking a
+            // spurious exception while still reporting the original error.
+            PyErr_Clear();
         }
 #else
         // PyErr_NormalizeException() may change the exception type if there are cascading
@@ -861,7 +865,7 @@
 }
 
 template <>
-inline bool isinstance<handle>(handle) = delete;
+bool isinstance<handle>(handle) = delete;
 template <>
 inline bool isinstance<object>(handle obj) {
     return obj.ptr() != nullptr;
@@ -992,9 +996,11 @@
     return rv;
 }
 
+// PyDict_GetItemStringRef was added in Python 3.13.0a1.
+// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
 inline PyObject *dict_getitemstringref(PyObject *v, const char *key) {
-#if PY_VERSION_HEX >= 0x030D0000
-    PyObject *rv;
+#if PY_VERSION_HEX >= 0x030D00A1
+    PyObject *rv = nullptr;
     if (PyDict_GetItemStringRef(v, key, &rv) < 0) {
         throw error_already_set();
     }
@@ -1009,6 +1015,46 @@
 #endif
 }
 
+inline PyObject *dict_setdefaultstring(PyObject *v, const char *key, PyObject *defaultobj) {
+    PyObject *kv = PyUnicode_FromString(key);
+    if (kv == nullptr) {
+        throw error_already_set();
+    }
+
+    PyObject *rv = PyDict_SetDefault(v, kv, defaultobj);
+    Py_DECREF(kv);
+    if (rv == nullptr) {
+        throw error_already_set();
+    }
+    return rv;
+}
+
+// PyDict_SetDefaultRef was added in Python 3.13.0a4.
+// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
+inline PyObject *dict_setdefaultstringref(PyObject *v, const char *key, PyObject *defaultobj) {
+#if PY_VERSION_HEX >= 0x030D00A4
+    PyObject *kv = PyUnicode_FromString(key);
+    if (kv == nullptr) {
+        throw error_already_set();
+    }
+
+    PyObject *rv = nullptr;
+    if (PyDict_SetDefaultRef(v, kv, defaultobj, &rv) < 0) {
+        Py_DECREF(kv);
+        throw error_already_set();
+    }
+    Py_DECREF(kv);
+    return rv;
+#else
+    PyObject *rv = dict_setdefaultstring(v, key, defaultobj);
+    if (rv == nullptr || PyErr_Occurred()) {
+        throw error_already_set();
+    }
+    Py_XINCREF(rv);
+    return rv;
+#endif
+}
+
 // Helper aliases/functions to support implicit casting of values given to python
 // accessors/methods. When given a pyobject, this simply returns the pyobject as-is; for other C++
 // type, the value goes through pybind11::cast(obj) to convert it to an `object`.
@@ -1039,11 +1085,11 @@
     void operator=(const accessor &a) & { operator=(handle(a)); }
 
     template <typename T>
-    void operator=(T &&value) && {
+    enable_if_t<!std::is_same<accessor, remove_reference_t<T>>::value> operator=(T &&value) && {
         Policy::set(obj, key, object_or_cast(std::forward<T>(value)));
     }
     template <typename T>
-    void operator=(T &&value) & {
+    enable_if_t<!std::is_same<accessor, remove_reference_t<T>>::value> operator=(T &&value) & {
         get_cache() = ensure_object(object_or_cast(std::forward<T>(value)));
     }
 
@@ -1555,7 +1601,7 @@
     }
 
 private:
-    object value = {};
+    object value;
 };
 
 class type : public object {
@@ -1563,7 +1609,9 @@
     PYBIND11_OBJECT(type, object, PyType_Check)
 
     /// Return a type handle from a handle or an object
-    static handle handle_of(handle h) { return handle((PyObject *) Py_TYPE(h.ptr())); }
+    static handle handle_of(handle h) {
+        return handle(reinterpret_cast<PyObject *>(Py_TYPE(h.ptr())));
+    }
 
     /// Return a type object from a handle or an object
     static type of(handle h) { return type(type::handle_of(h), borrowed_t{}); }
@@ -1641,7 +1689,12 @@
         Return a string representation of the object. This is analogous to
         the ``str()`` function in Python.
     \endrst */
-    explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) {
+    // Templatized to avoid ambiguity with str(const object&) for object-derived types.
+    template <typename T,
+              detail::enable_if_t<!std::is_base_of<object, detail::remove_cvref_t<T>>::value
+                                      && std::is_constructible<handle, T>::value,
+                                  int> = 0>
+    explicit str(T &&h) : object(raw_str(handle(std::forward<T>(h)).ptr()), stolen_t{}) {
         if (!m_ptr) {
             throw error_already_set();
         }
@@ -1661,7 +1714,7 @@
         if (PyBytes_AsStringAndSize(temp.ptr(), &buffer, &length) != 0) {
             throw error_already_set();
         }
-        return std::string(buffer, (size_t) length);
+        return std::string(buffer, static_cast<size_t>(length));
     }
 
     template <typename... Args>
@@ -1861,10 +1914,12 @@
 Unsigned as_unsigned(PyObject *o) {
     if (sizeof(Unsigned) <= sizeof(unsigned long)) {
         unsigned long v = PyLong_AsUnsignedLong(o);
-        return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
+        return v == static_cast<unsigned long>(-1) && PyErr_Occurred() ? (Unsigned) -1
+                                                                       : (Unsigned) v;
     }
     unsigned long long v = PyLong_AsUnsignedLongLong(o);
-    return v == (unsigned long long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
+    return v == static_cast<unsigned long long>(-1) && PyErr_Occurred() ? (Unsigned) -1
+                                                                        : (Unsigned) v;
 }
 PYBIND11_NAMESPACE_END(detail)
 
@@ -1908,21 +1963,21 @@
     PYBIND11_OBJECT_CVT(float_, object, PyFloat_Check, PyNumber_Float)
     // Allow implicit conversion from float/double:
     // NOLINTNEXTLINE(google-explicit-constructor)
-    float_(float value) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
+    float_(float value) : object(PyFloat_FromDouble(static_cast<double>(value)), stolen_t{}) {
         if (!m_ptr) {
             pybind11_fail("Could not allocate float object!");
         }
     }
     // NOLINTNEXTLINE(google-explicit-constructor)
-    float_(double value = .0) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
+    float_(double value = .0) : object(PyFloat_FromDouble(value), stolen_t{}) {
         if (!m_ptr) {
             pybind11_fail("Could not allocate float object!");
         }
     }
     // NOLINTNEXTLINE(google-explicit-constructor)
-    operator float() const { return (float) PyFloat_AsDouble(m_ptr); }
+    operator float() const { return static_cast<float>(PyFloat_AsDouble(m_ptr)); }
     // NOLINTNEXTLINE(google-explicit-constructor)
-    operator double() const { return (double) PyFloat_AsDouble(m_ptr); }
+    operator double() const { return PyFloat_AsDouble(m_ptr); }
 };
 
 class weakref : public object {
@@ -2122,7 +2177,7 @@
             pybind11_fail("Could not allocate tuple object!");
         }
     }
-    size_t size() const { return (size_t) PyTuple_Size(m_ptr); }
+    size_t size() const { return static_cast<size_t>(PyTuple_Size(m_ptr)); }
     bool empty() const { return size() == 0; }
     detail::tuple_accessor operator[](size_t index) const { return {*this, index}; }
     template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
@@ -2156,7 +2211,7 @@
               typename collector = detail::deferred_t<detail::unpacking_collector<>, Args...>>
     explicit dict(Args &&...args) : dict(collector(std::forward<Args>(args)...).kwargs()) {}
 
-    size_t size() const { return (size_t) PyDict_Size(m_ptr); }
+    size_t size() const { return static_cast<size_t>(PyDict_Size(m_ptr)); }
     bool empty() const { return size() == 0; }
     detail::dict_iterator begin() const { return {*this, 0}; }
     detail::dict_iterator end() const { return {}; }
@@ -2176,7 +2231,8 @@
         if (PyDict_Check(op)) {
             return handle(op).inc_ref().ptr();
         }
-        return PyObject_CallFunctionObjArgs((PyObject *) &PyDict_Type, op, nullptr);
+        return PyObject_CallFunctionObjArgs(
+            reinterpret_cast<PyObject *>(&PyDict_Type), op, nullptr);
     }
 };
 
@@ -2188,7 +2244,7 @@
         if (result == -1) {
             throw error_already_set();
         }
-        return (size_t) result;
+        return static_cast<size_t>(result);
     }
     bool empty() const { return size() == 0; }
     detail::sequence_accessor operator[](size_t index) const { return {*this, index}; }
@@ -2211,7 +2267,7 @@
             pybind11_fail("Could not allocate list object!");
         }
     }
-    size_t size() const { return (size_t) PyList_Size(m_ptr); }
+    size_t size() const { return static_cast<size_t>(PyList_Size(m_ptr)); }
     bool empty() const { return size() == 0; }
     detail::list_accessor operator[](size_t index) const { return {*this, index}; }
     template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
@@ -2497,7 +2553,7 @@
     if (result < 0) {
         throw error_already_set();
     }
-    return (size_t) result;
+    return static_cast<size_t>(result);
 }
 
 /// Get the length hint of a Python object.
@@ -2510,7 +2566,7 @@
         PyErr_Clear();
         return 0;
     }
-    return (size_t) result;
+    return static_cast<size_t>(result);
 }
 
 inline str repr(handle h) {
diff -Nru pybind11-3.0.1/include/pybind11/stl/filesystem.h pybind11-3.0.4/include/pybind11/stl/filesystem.h
--- pybind11-3.0.1/include/pybind11/stl/filesystem.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/stl/filesystem.h	2026-04-19 06:01:51.000000000 +0300
@@ -17,7 +17,7 @@
 #elif defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
 #    include <experimental/filesystem>
 #else
-#    error "Neither #include <filesystem> nor #include <experimental/filesystem is available."
+#    error "Neither #include <filesystem> nor #include <experimental/filesystem> is available."
 #endif
 
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
diff -Nru pybind11-3.0.1/include/pybind11/stl_bind.h pybind11-3.0.4/include/pybind11/stl_bind.h
--- pybind11-3.0.1/include/pybind11/stl_bind.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/stl_bind.h	2026-04-19 06:01:51.000000000 +0300
@@ -244,7 +244,7 @@
             }
 
             auto *seq = new Vector();
-            seq->reserve((size_t) slicelength);
+            seq->reserve(slicelength);
 
             for (size_t i = 0; i < slicelength; ++i) {
                 seq->push_back(v[start]);
diff -Nru pybind11-3.0.1/include/pybind11/subinterpreter.h pybind11-3.0.4/include/pybind11/subinterpreter.h
--- pybind11-3.0.1/include/pybind11/subinterpreter.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/subinterpreter.h	2026-04-19 06:01:51.000000000 +0300
@@ -20,15 +20,6 @@
 #endif
 
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
-PYBIND11_NAMESPACE_BEGIN(detail)
-inline PyInterpreterState *get_interpreter_state_unchecked() {
-    auto cur_tstate = get_thread_state_unchecked();
-    if (cur_tstate)
-        return cur_tstate->interp;
-    else
-        return nullptr;
-}
-PYBIND11_NAMESPACE_END(detail)
 
 class subinterpreter;
 
@@ -76,7 +67,7 @@
     /// Create a new subinterpreter with the specified configuration
     /// @note This function acquires (and then releases) the main interpreter GIL, but the main
     /// interpreter and its GIL are not required to be held prior to calling this function.
-    static inline subinterpreter create(PyInterpreterConfig const &cfg) {
+    static subinterpreter create(PyInterpreterConfig const &cfg) {
 
         error_scope err_scope;
         subinterpreter result;
@@ -84,7 +75,7 @@
             // we must hold the main GIL in order to create a subinterpreter
             subinterpreter_scoped_activate main_guard(main());
 
-            auto prev_tstate = PyThreadState_Get();
+            auto *prev_tstate = PyThreadState_Get();
 
             PyStatus status;
 
@@ -103,13 +94,13 @@
             }
 
             // this doesn't raise a normal Python exception, it provides an exit() status code.
-            if (PyStatus_Exception(status)) {
+            if (PyStatus_Exception(status) != 0) {
                 pybind11_fail("failed to create new sub-interpreter");
             }
 
             // upon success, the new interpreter is activated in this thread
             result.istate_ = result.creation_tstate_->interp;
-            detail::get_num_interpreters_seen() += 1; // there are now many interpreters
+            detail::has_seen_non_main_interpreter() = true;
             detail::get_internals(); // initialize internals.tstate, amongst other things...
 
             // In 3.13+ this state should be deleted right away, and the memory will be reused for
@@ -128,7 +119,7 @@
 
     /// Calls create() with a default configuration of an isolated interpreter that disallows fork,
     /// exec, and Python threads.
-    static inline subinterpreter create() {
+    static subinterpreter create() {
         // same as the default config in the python docs
         PyInterpreterConfig cfg;
         std::memset(&cfg, 0, sizeof(cfg));
@@ -144,8 +135,8 @@
             return;
         }
 
-        PyThreadState *destroy_tstate;
-        PyThreadState *old_tstate;
+        PyThreadState *destroy_tstate = nullptr;
+        PyThreadState *old_tstate = nullptr;
 
         // Python 3.12 requires us to keep the original PyThreadState alive until we are ready to
         // destroy the interpreter.  We prefer to use that to destroy the interpreter.
@@ -173,7 +164,7 @@
         old_tstate = PyThreadState_Swap(destroy_tstate);
 #endif
 
-        bool switch_back = old_tstate && old_tstate->interp != istate_;
+        bool switch_back = (old_tstate != nullptr) && old_tstate->interp != istate_;
 
         // Internals always exists in the subinterpreter, this class enforces it when it creates
         // the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
@@ -190,8 +181,9 @@
         detail::get_local_internals_pp_manager().destroy();
 
         // switch back to the old tstate and old GIL (if there was one)
-        if (switch_back)
+        if (switch_back) {
             PyThreadState_Swap(old_tstate);
+        }
     }
 
     /// Get a handle to the main interpreter that can be used with subinterpreter_scoped_activate
@@ -214,11 +206,11 @@
 
     /// Get the numerical identifier for the sub-interpreter
     int64_t id() const {
-        if (istate_ != nullptr)
+        if (istate_ != nullptr) {
             return PyInterpreterState_GetID(istate_);
-        else
-            return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
-                       // here.
+        }
+        return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
+                   // here.
     }
 
     /// Get the interpreter's state dict.  This interpreter's GIL must be held before calling!
@@ -271,7 +263,7 @@
     // make the interpreter active and acquire the GIL
     old_tstate_ = PyThreadState_Swap(tstate_);
 
-    // save this in internals for scoped_gil calls
+    // save this in internals for scoped_gil calls (see also: PR #5870)
     detail::get_internals().tstate = tstate_;
 }
 
diff -Nru pybind11-3.0.1/include/pybind11/typing.h pybind11-3.0.4/include/pybind11/typing.h
--- pybind11-3.0.1/include/pybind11/typing.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/include/pybind11/typing.h	2026-04-19 06:01:51.000000000 +0300
@@ -34,10 +34,7 @@
     There is no additional enforcement of types at runtime.
 */
 
-template <typename... Types>
-class Tuple : public tuple {
-    using tuple::tuple;
-};
+// Tuple type hint defined in cast.h for use in py::make_tuple to avoid circular includes
 
 template <typename K, typename V>
 class Dict : public dict {
diff -Nru pybind11-3.0.1/.pre-commit-config.yaml pybind11-3.0.4/.pre-commit-config.yaml
--- pybind11-3.0.1/.pre-commit-config.yaml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/.pre-commit-config.yaml	2026-04-19 06:01:51.000000000 +0300
@@ -25,14 +25,14 @@
 
 # Clang format the codebase automatically
 - repo: https://github.com/pre-commit/mirrors-clang-format
-  rev: "v20.1.8"
+  rev: "v22.1.2"
   hooks:
   - id: clang-format
     types_or: [c++, c, cuda]
 
 # Ruff, the Python auto-correcting linter/formatter written in Rust
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.12.7
+  rev: v0.15.9
   hooks:
   - id: ruff-check
     args: ["--fix", "--show-fixes"]
@@ -40,7 +40,7 @@
 
 # Check static types with mypy
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: "v1.17.1"
+  rev: "v1.20.0"
   hooks:
   - id: mypy
     args: []
@@ -62,7 +62,7 @@
 
 # Standard hooks
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: "v5.0.0"
+  rev: "v6.0.0"
   hooks:
   - id: check-added-large-files
   - id: check-case-conflict
@@ -80,7 +80,7 @@
 
 # Also code format the docs
 - repo: https://github.com/adamchainz/blacken-docs
-  rev: "1.19.1"
+  rev: "1.20.0"
   hooks:
   - id: blacken-docs
     additional_dependencies:
@@ -88,7 +88,7 @@
 
 # Changes tabs to spaces
 - repo: https://github.com/Lucas-C/pre-commit-hooks
-  rev: "v1.5.5"
+  rev: "v1.5.6"
   hooks:
   - id: remove-tabs
     exclude: (^docs/.*|\.patch)?$
@@ -112,15 +112,24 @@
 # Use tools/codespell_ignore_lines_from_errors.py
 # to rebuild .codespell-ignore-lines
 - repo: https://github.com/codespell-project/codespell
-  rev: "v2.4.1"
+  rev: "v2.4.2"
   hooks:
   - id: codespell
-    exclude: ".supp$"
+    exclude: "(.supp|^pyproject.toml)$"
     args: ["-x.codespell-ignore-lines", "-Lccompiler,intstruct"]
 
+# Also check spelling
+# Use mirror because pre-commit autoupdate confuses tags in the upstream repo.
+# See https://github.com/crate-ci/typos/issues/390
+- repo: https://github.com/adhtruong/mirrors-typos
+  rev: "v1.45.0"
+  hooks:
+  - id: typos
+    args: []
+
 # Check for common shell mistakes
 - repo: https://github.com/shellcheck-py/shellcheck-py
-  rev: "v0.10.0.1"
+  rev: "v0.11.0.1"
   hooks:
   - id: shellcheck
 
@@ -135,14 +144,14 @@
 
 # PyLint has native support - not always usable, but works for us
 - repo: https://github.com/PyCQA/pylint
-  rev: "v3.3.7"
+  rev: "v4.0.5"
   hooks:
   - id: pylint
     files: ^pybind11
 
 # Check schemas on some of our YAML files
 - repo: https://github.com/python-jsonschema/check-jsonschema
-  rev: 0.33.2
+  rev: 0.37.1
   hooks:
   - id: check-readthedocs
   - id: check-github-workflows
diff -Nru pybind11-3.0.1/pyproject.toml pybind11-3.0.4/pyproject.toml
--- pybind11-3.0.1/pyproject.toml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/pyproject.toml	2026-04-19 06:01:51.000000000 +0300
@@ -38,7 +38,7 @@
 [project.urls]
 Homepage = "https://github.com/pybind/pybind11"
 Documentation = "https://pybind11.readthedocs.io/"
-"Bug Tracker" = "https://github.com/pybind/pybind11/issues"
+"Issue Tracker" = "https://github.com/pybind/pybind11/issues"
 Discussions = "https://github.com/pybind/pybind11/discussions"
 Changelog = "https://pybind11.readthedocs.io/en/latest/changelog.html"
 Chat = "https://gitter.im/pybind/Lobby"
@@ -57,9 +57,11 @@
 test = [
   "pytest",
   "build",
+]
+dev = [
   "tomlkit",
+  { include-group = "test" }
 ]
-dev = [{ include-group = "test" }]
 
 
 [tool.scikit-build]
@@ -152,6 +154,8 @@
   "C4",          # flake8-comprehensions
   "EM",          # flake8-errmsg
   "ICN",         # flake8-import-conventions
+  "ISC",         # flake8-implicit-str-concat
+  "PERF",        # Perflint
   "PGH",         # pygrep-hooks
   "PIE",         # flake8-pie
   "PL",          # pylint
@@ -182,3 +186,30 @@
 
 [tool.repo-review]
 ignore = ["PP"]
+
+[tool.typos]
+files.extend-exclude = ["/cpython"]
+
+[tool.typos.default.extend-identifiers]
+ser_no = "ser_no"
+SerNo = "SerNo"
+StrLits = "StrLits"
+
+[tool.typos.default.extend-words]
+nd = "nd"
+valu = "valu"
+fo = "fo"
+quater = "quater"
+optin = "optin"
+othr = "othr"
+# NumPy uses "writeable" in public API names and flags.
+writeable = "writeable"
+Writeable = "Writeable"
+WRITEABLE = "WRITEABLE"
+
+#[tool.typos.type.cpp.extend-words]
+setp = "setp"
+ot = "ot"
+
+[tool.typos.type.json.extend-words]
+ba = "ba"
diff -Nru pybind11-3.0.1/README.rst pybind11-3.0.4/README.rst
--- pybind11-3.0.1/README.rst	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/README.rst	2026-04-19 06:01:51.000000000 +0300
@@ -120,24 +120,23 @@
 - With little extra effort, C++ types can be pickled and unpickled
   similar to regular Python objects.
 
-Supported compilers
--------------------
+Supported platforms & compilers
+-------------------------------
 
-1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or
-   newer)
-2. GCC 4.8 or newer
-3. Microsoft Visual Studio 2022 or newer (2019 probably works, but was dropped in CI)
-4. Intel classic C++ compiler 18 or newer (ICC 20.2 tested in CI)
-5. Cygwin/GCC (previously tested on 2.5.1)
-6. NVCC (CUDA 11.0 tested in CI)
-7. NVIDIA PGI (20.9 tested in CI)
+pybind11 is exercised in continuous integration across a range of operating
+systems, Python versions, C++ standards, and toolchains. For an up-to-date
+view of the combinations we currently test, please see the
+`pybind11 GitHub Actions <https://github.com/pybind/pybind11/actions?query=branch%3Amaster>`_
+logs.
 
-Supported Platforms
--------------------
-
-* Windows, Linux, macOS, and iOS
-* CPython 3.8+, Pyodide, PyPy, and GraalPy
-* C++11, C++14, C++17, C++20, and C++23
+The test matrix naturally evolves over time as older platforms and compilers
+fall out of use and new ones are added by the community. Closely related
+versions of a tested compiler or platform will often work as well in practice,
+but we cannot promise to validate every possible combination. If a
+configuration you rely on is missing from the matrix or regresses, issues and
+pull requests to extend coverage are very welcome. At the same time, we need
+to balance the size of the test matrix with the available CI resources,
+such as GitHub's limits on concurrent jobs under the free tier.
 
 About
 -----
diff -Nru pybind11-3.0.1/tests/CMakeLists.txt pybind11-3.0.4/tests/CMakeLists.txt
--- pybind11-3.0.1/tests/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -5,7 +5,7 @@
 # All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 # Filter out items; print an optional message if any items filtered. This ignores extensions.
 #
@@ -117,6 +117,7 @@
     test_callbacks
     test_chrono
     test_class
+    test_class_cross_module_use_after_one_module_dealloc
     test_class_release_gil_before_calling_cpp_dtor
     test_class_sh_basic
     test_class_sh_disowning
@@ -166,11 +167,13 @@
     test_operator_overloading
     test_pickling
     test_potentially_slicing_weak_ptr
+    test_pytorch_shared_ptr_cast_regression
     test_python_multiple_inheritance
     test_pytypes
     test_scoped_critical_section
     test_sequences_and_iterators
     test_smart_ptr
+    test_standalone_enum_module.py
     test_stl
     test_stl_binders
     test_tagbased_polymorphic
@@ -239,14 +242,16 @@
 # Contains the set of test files that require pybind11_cross_module_tests to be
 # built; if none of these are built (i.e. because TEST_OVERRIDE is used and
 # doesn't include them) the second module doesn't get built.
-tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_stl_binders.py"
-                    "pybind11_cross_module_tests")
+tests_extra_targets(
+  "test_class_cross_module_use_after_one_module_dealloc.py;test_exceptions.py;test_local_bindings.py;test_stl.py;test_stl_binders.py"
+  "pybind11_cross_module_tests")
 
 # And add additional targets for other tests.
 tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set")
 tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils")
 tests_extra_targets("test_cpp_conduit.py"
                     "exo_planet_pybind11;exo_planet_c_api;home_planet_very_lonely_traveler")
+tests_extra_targets("test_standalone_enum_module.py" "standalone_enum_module")
 
 set(PYBIND11_EIGEN_REPO
     "https://gitlab.com/libeigen/eigen.git"
@@ -295,10 +300,17 @@
 
   else()
     find_package(Eigen3 3.2.7 QUIET CONFIG)
+    if(NOT Eigen3_FOUND)
+      find_package(Eigen3 5 QUIET CONFIG)
+    endif()
+    set(EIGEN3_FOUND ${Eigen3_FOUND})
+    set(EIGEN3_VERSION ${Eigen3_VERSION})
 
     if(NOT EIGEN3_FOUND)
       # Couldn't load via target, so fall back to allowing module mode finding, which will pick up
       # tools/FindEigen3.cmake
+      # This MODULE-mode fallback is for older Eigen 3 setups; Eigen 5 is expected to be found
+      # via the CONFIG-mode probes above.
       find_package(Eigen3 3.2.7 QUIET)
     endif()
   endif()
@@ -543,7 +555,9 @@
   FILES ${PYBIND11_HEADERS})
 
 # Make sure pytest is found or produce a warning
-pybind11_find_import(pytest VERSION 3.1)
+if(NOT CMAKE_CROSSCOMPILING)
+  pybind11_find_import(pytest VERSION 3.1)
+endif()
 
 if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
   # This is not used later in the build, so it's okay to regenerate each time.
@@ -576,24 +590,46 @@
   USES_TERMINAL)
 
 if(NOT PYBIND11_CUDA_TESTS)
-  # This module doesn't get mixed with other test modules because those aren't subinterpreter safe.
-  pybind11_add_module(mod_per_interpreter_gil THIN_LTO mod_per_interpreter_gil.cpp)
-  pybind11_add_module(mod_shared_interpreter_gil THIN_LTO mod_shared_interpreter_gil.cpp)
-  set_target_properties(mod_per_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
-                                                           "$<1:${CMAKE_CURRENT_BINARY_DIR}>")
-  set_target_properties(mod_shared_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
-                                                              "$<1:${CMAKE_CURRENT_BINARY_DIR}>")
+  set(PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES
+      mod_per_interpreter_gil mod_shared_interpreter_gil mod_per_interpreter_gil_with_singleton)
+
+  # These modules don't get mixed with other test modules because those aren't subinterpreter safe.
+  foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
+    pybind11_add_module("${mod}" THIN_LTO "${mod}.cpp")
+    pybind11_enable_warnings("${mod}")
+  endforeach()
+
+  # Put the built modules next to `pybind11_tests.so` so that the test scripts can find them.
+  get_target_property(pybind11_tests_output_directory pybind11_tests LIBRARY_OUTPUT_DIRECTORY)
+  foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
+    set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY
+                                              "${pybind11_tests_output_directory}")
+    # Also set config-specific output directories for multi-configuration generators (MSVC)
+    if(DEFINED CMAKE_CONFIGURATION_TYPES)
+      foreach(config ${CMAKE_CONFIGURATION_TYPES})
+        string(TOUPPER ${config} config)
+        set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
+                                                  "${pybind11_tests_output_directory}")
+      endforeach()
+    endif()
+  endforeach()
+
+  if(SKBUILD)
+    foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
+      install(TARGETS "${mod}" LIBRARY DESTINATION .)
+    endforeach()
+  endif()
+
   if(PYBIND11_TEST_SMART_HOLDER)
-    target_compile_definitions(
-      mod_per_interpreter_gil
-      PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
-    )
-    target_compile_definitions(
-      mod_shared_interpreter_gil
-      PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
-    )
+    foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
+      target_compile_definitions(
+        "${mod}"
+        PUBLIC
+          -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
+    endforeach()
   endif()
-  add_dependencies(pytest mod_per_interpreter_gil mod_shared_interpreter_gil)
+
+  add_dependencies(pytest ${PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES})
 endif()
 
 if(PYBIND11_TEST_OVERRIDE)
@@ -647,8 +683,8 @@
   # Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target.
   add_subdirectory(pure_cpp)
 
-  # Test embedding the interpreter. Provides the `cpptest` target.
-  add_subdirectory(test_embed)
+  # Test C++ code that depends on Python, such as embedding the interpreter. Provides the `cpptest` target.
+  add_subdirectory(test_with_catch)
 
   # Test CMake build using functions and targets from subdirectory or installed location
   add_subdirectory(test_cmake_build)
diff -Nru pybind11-3.0.1/tests/conftest.py pybind11-3.0.4/tests/conftest.py
--- pybind11-3.0.1/tests/conftest.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/conftest.py	2026-04-19 06:01:51.000000000 +0300
@@ -16,6 +16,7 @@
 import sysconfig
 import textwrap
 import traceback
+import weakref
 from typing import Callable
 
 import pytest
@@ -208,19 +209,54 @@
     return None
 
 
+# Number of times we think repeatedly collecting garbage might do anything.
+# The only reason to do more than once is because finalizers executed during
+# one GC pass could create garbage that can't be collected until a future one.
+# This quickly produces diminishing returns, and GC passes can be slow, so this
+# value is a tradeoff between non-flakiness and fast tests. (It errs on the
+# side of non-flakiness; many uses of this idiom only do 3 passes.)
+num_gc_collect = 5
+
+
 def gc_collect():
-    """Run the garbage collector three times (needed when running
+    """Run the garbage collector several times (needed when running
     reference counting tests with PyPy)"""
-    gc.collect()
-    gc.collect()
-    gc.collect()
-    gc.collect()
-    gc.collect()
+    for _ in range(num_gc_collect):
+        gc.collect()
+
+
+def delattr_and_ensure_destroyed(*specs):
+    """For each of the given *specs* (a tuple of the form ``(scope, name)``),
+    perform ``delattr(scope, name)``, then do enough GC collections that the
+    deleted reference has actually caused the target to be destroyed. This is
+    typically used to test what happens when a type object is destroyed; if you
+    use it for that, you should be aware that extension types, or all types,
+    are immortal on some Python versions. See ``env.TYPES_ARE_IMMORTAL``.
+    """
+    wrs = []
+    for mod, name in specs:
+        wrs.append(weakref.ref(getattr(mod, name)))
+        delattr(mod, name)
+
+    for _ in range(num_gc_collect):
+        gc.collect()
+        if all(wr() is None for wr in wrs):
+            break
+    else:
+        # If this fires, most likely something is still holding a reference
+        # to the object you tried to destroy - for example, it's a type that
+        # still has some instances alive. Try setting a breakpoint here and
+        # examining `gc.get_referrers(wrs[0]())`. It's vaguely possible that
+        # num_gc_collect needs to be increased also.
+        pytest.fail(
+            f"Could not delete bindings such as {next(wr for wr in wrs if wr() is not None)!r}"
+        )
 
 
 def pytest_configure():
     pytest.suppress = contextlib.suppress
     pytest.gc_collect = gc_collect
+    pytest.delattr_and_ensure_destroyed = delattr_and_ensure_destroyed
 
 
 def pytest_report_header():
@@ -268,10 +304,10 @@
     if sys.version_info < (3, 10):
         d["typing_extensions.TypeGuard"] = "typing.TypeGuard"
 
-    def backport(sanatized_string: SanitizedString) -> SanitizedString:
+    def backport(sanitized_string: SanitizedString) -> SanitizedString:
         for old, new in d.items():
-            sanatized_string.string = sanatized_string.string.replace(old, new)
+            sanitized_string.string = sanitized_string.string.replace(old, new)
 
-        return sanatized_string
+        return sanitized_string
 
     return backport
diff -Nru pybind11-3.0.1/tests/env.py pybind11-3.0.4/tests/env.py
--- pybind11-3.0.1/tests/env.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/env.py	2026-04-19 06:01:51.000000000 +0300
@@ -7,9 +7,23 @@
 import pytest
 
 ANDROID = sys.platform.startswith("android")
+IOS = sys.platform.startswith("ios")
 LINUX = sys.platform.startswith("linux")
 MACOS = sys.platform.startswith("darwin")
 WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
+FREEBSD = sys.platform.startswith("freebsd")
+
+MUSLLINUX = False
+MANYLINUX = False
+if LINUX:
+
+    def _is_musl() -> bool:
+        libc, _ = platform.libc_ver()
+        return libc == "musl" or (libc != "glibc" and libc != "")
+
+    MUSLLINUX = _is_musl()
+    MANYLINUX = not MUSLLINUX
+    del _is_musl
 
 CPYTHON = platform.python_implementation() == "CPython"
 PYPY = platform.python_implementation() == "PyPy"
@@ -18,20 +32,45 @@
     sys.modules["__graalpython__"].get_graalvm_version() if GRAALPY else "0.0.0"
 )
 GRAALPY_VERSION = tuple(int(t) for t in _graalpy_version.split("-")[0].split(".")[:3])
+
+# Compile-time config (what the binary was built for)
 PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
+# Runtime state (what's actually happening now)
+sys_is_gil_enabled = getattr(sys, "_is_gil_enabled", lambda: True)
+
+TYPES_ARE_IMMORTAL = (
+    PYPY
+    or GRAALPY
+    or (CPYTHON and PY_GIL_DISABLED and (3, 13) <= sys.version_info < (3, 14))
+)
+
+
+def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
+    """Runs the given code in a subprocess."""
+    import os
+    import subprocess
+    import sys
+    import textwrap
 
+    if ANDROID or IOS or sys.platform.startswith("emscripten"):
+        pytest.skip("Requires subprocess support")
 
-def deprecated_call():
-    """
-    pytest.deprecated_call() seems broken in pytest<3.9.x; concretely, it
-    doesn't work on CPython 3.8.0 with pytest==3.3.2 on Ubuntu 18.04 (#2922).
-
-    This is a narrowed reimplementation of the following PR :(
-    https://github.com/pytest-dev/pytest/pull/4104
-    """
-    # TODO: Remove this when testing requires pytest>=3.9.
-    pieces = pytest.__version__.split(".")
-    pytest_major_minor = (int(pieces[0]), int(pieces[1]))
-    if pytest_major_minor < (3, 9):
-        return pytest.warns((DeprecationWarning, PendingDeprecationWarning))
-    return pytest.deprecated_call()
+    code = textwrap.dedent(code).strip()
+    try:
+        for _ in range(rerun):  # run flakily failing test multiple times
+            subprocess.check_output(
+                [sys.executable, "-c", code],
+                cwd=os.getcwd(),
+                stderr=subprocess.STDOUT,
+                text=True,
+            )
+    except subprocess.CalledProcessError as ex:
+        raise RuntimeError(
+            f"Subprocess failed with exit code {ex.returncode}.\n\n"
+            f"Code:\n"
+            f"```python\n"
+            f"{code}\n"
+            f"```\n\n"
+            f"Output:\n"
+            f"{ex.output}"
+        ) from None
diff -Nru pybind11-3.0.1/tests/extra_python_package/test_files.py pybind11-3.0.4/tests/extra_python_package/test_files.py
--- pybind11-3.0.1/tests/extra_python_package/test_files.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/extra_python_package/test_files.py	2026-04-19 06:01:51.000000000 +0300
@@ -76,12 +76,14 @@
 }
 
 detail_headers = {
+    "include/pybind11/detail/argument_vector.h",
     "include/pybind11/detail/class.h",
     "include/pybind11/detail/common.h",
     "include/pybind11/detail/cpp_conduit.h",
     "include/pybind11/detail/descr.h",
     "include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
     "include/pybind11/detail/function_record_pyobject.h",
+    "include/pybind11/detail/holder_caster_foreign_helpers.h",
     "include/pybind11/detail/init.h",
     "include/pybind11/detail/internals.h",
     "include/pybind11/detail/native_enum_data.h",
diff -Nru pybind11-3.0.1/tests/local_bindings.h pybind11-3.0.4/tests/local_bindings.h
--- pybind11-3.0.1/tests/local_bindings.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/local_bindings.h	2026-04-19 06:01:51.000000000 +0300
@@ -25,8 +25,9 @@
 using MixedGlobalLocal = LocalBase<5>;
 
 /// Registered with py::module_local only in the secondary module:
-using ExternalType1 = LocalBase<6>;
-using ExternalType2 = LocalBase<7>;
+using ExternalType1 = LocalBase<6>; // default holder
+using ExternalType2 = LocalBase<7>; // held by std::shared_ptr
+using ExternalType3 = LocalBase<8>; // held by smart_holder
 
 using LocalVec = std::vector<LocalType>;
 using LocalVec2 = std::vector<NonLocal2>;
@@ -65,11 +66,11 @@
 PYBIND11_MAKE_OPAQUE(NonLocalMap2)
 
 // Simple bindings (used with the above):
-template <typename T, int Adjust = 0, typename... Args>
-py::class_<T> bind_local(Args &&...args) {
-    return py::class_<T>(std::forward<Args>(args)...).def(py::init<int>()).def("get", [](T &i) {
-        return i.i + Adjust;
-    });
+template <typename T, int Adjust = 0, typename Holder = std::unique_ptr<T>, typename... Args>
+py::class_<T, Holder> bind_local(Args &&...args) {
+    return py::class_<T, Holder>(std::forward<Args>(args)...)
+        .def(py::init<int>())
+        .def("get", [](T &i) { return i.i + Adjust; });
 }
 
 // Simulate a foreign library base class (to match the example in the docs):
diff -Nru pybind11-3.0.1/tests/mod_per_interpreter_gil_with_singleton.cpp pybind11-3.0.4/tests/mod_per_interpreter_gil_with_singleton.cpp
--- pybind11-3.0.1/tests/mod_per_interpreter_gil_with_singleton.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/mod_per_interpreter_gil_with_singleton.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,147 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include <vector>
+
+namespace py = pybind11;
+
+#ifdef PYBIND11_HAS_NATIVE_ENUM
+#    include <pybind11/native_enum.h>
+#endif
+
+namespace pybind11_tests {
+namespace mod_per_interpreter_gil_with_singleton {
+// A singleton class that holds references to certain Python objects
+// This singleton is per-interpreter using gil_safe_call_once_and_store
+class MySingleton {
+public:
+    MySingleton() = default;
+    ~MySingleton() = default;
+    MySingleton(const MySingleton &) = delete;
+    MySingleton &operator=(const MySingleton &) = delete;
+    MySingleton(MySingleton &&) = default;
+    MySingleton &operator=(MySingleton &&) = default;
+
+    static MySingleton &get_instance() {
+        PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<MySingleton> storage;
+        return storage
+            .call_once_and_store_result([]() -> MySingleton {
+                MySingleton instance{};
+
+                auto emplace = [&instance](const py::handle &obj) -> void {
+                    obj.inc_ref(); // Ensure the object is not GC'd while interpreter is alive
+                    instance.objects.emplace_back(obj);
+                };
+
+                // Example objects to store in the singleton
+                emplace(py::type::handle_of(py::none()));                        // static type
+                emplace(py::type::handle_of(py::tuple()));                       // static type
+                emplace(py::type::handle_of(py::list()));                        // static type
+                emplace(py::type::handle_of(py::dict()));                        // static type
+                emplace(py::module_::import("collections").attr("OrderedDict")); // static type
+                emplace(py::module_::import("collections").attr("defaultdict")); // heap type
+                emplace(py::module_::import("collections").attr("deque"));       // heap type
+
+                assert(instance.objects.size() == 7);
+                return instance;
+            })
+            .get_stored();
+    }
+
+    std::vector<py::handle> &get_objects() { return objects; }
+
+    static void init() {
+        // Ensure the singleton is created
+        auto &instance = get_instance();
+        (void) instance; // suppress unused variable warning
+        assert(instance.objects.size() == 7);
+        // Register cleanup at interpreter exit
+        py::module_::import("atexit").attr("register")(py::cpp_function(&MySingleton::clear));
+    }
+
+    static void clear() {
+        auto &instance = get_instance();
+        (void) instance; // suppress unused variable warning
+        assert(instance.objects.size() == 7);
+        for (const auto &obj : instance.objects) {
+            obj.dec_ref();
+        }
+        instance.objects.clear();
+    }
+
+private:
+    std::vector<py::handle> objects;
+};
+
+class MyClass {
+public:
+    explicit MyClass(py::ssize_t v) : value(v) {}
+    py::ssize_t get_value() const { return value; }
+
+private:
+    py::ssize_t value;
+};
+
+class MyGlobalError : public std::runtime_error {
+public:
+    using std::runtime_error::runtime_error;
+};
+
+class MyLocalError : public std::runtime_error {
+public:
+    using std::runtime_error::runtime_error;
+};
+
+enum class MyEnum : int {
+    ONE = 1,
+    TWO = 2,
+    THREE = 3,
+};
+} // namespace mod_per_interpreter_gil_with_singleton
+} // namespace pybind11_tests
+
+PYBIND11_MODULE(mod_per_interpreter_gil_with_singleton,
+                m,
+                py::mod_gil_not_used(),
+                py::multiple_interpreters::per_interpreter_gil()) {
+    using namespace pybind11_tests::mod_per_interpreter_gil_with_singleton;
+
+#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
+    m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = true;
+#else
+    m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = false;
+#endif
+
+    MySingleton::init();
+
+    // Ensure py::multiple_interpreters::per_interpreter_gil() works with singletons using
+    // py::gil_safe_call_once_and_store
+    m.def(
+        "get_objects_in_singleton",
+        []() -> std::vector<py::handle> { return MySingleton::get_instance().get_objects(); },
+        "Get the list of objects stored in the singleton");
+
+    // Ensure py::multiple_interpreters::per_interpreter_gil() works with class bindings
+    py::class_<MyClass>(m, "MyClass")
+        .def(py::init<py::ssize_t>())
+        .def("get_value", &MyClass::get_value);
+
+    // Ensure py::multiple_interpreters::per_interpreter_gil() works with global exceptions
+    py::register_exception<MyGlobalError>(m, "MyGlobalError");
+    // Ensure py::multiple_interpreters::per_interpreter_gil() works with local exceptions
+    py::register_local_exception<MyLocalError>(m, "MyLocalError");
+
+#ifdef PYBIND11_HAS_NATIVE_ENUM
+    // Ensure py::multiple_interpreters::per_interpreter_gil() works with native_enum
+    py::native_enum<MyEnum>(m, "MyEnum", "enum.IntEnum")
+        .value("ONE", MyEnum::ONE)
+        .value("TWO", MyEnum::TWO)
+        .value("THREE", MyEnum::THREE)
+        .finalize();
+#else
+    py::enum_<MyEnum>(m, "MyEnum")
+        .value("ONE", MyEnum::ONE)
+        .value("TWO", MyEnum::TWO)
+        .value("THREE", MyEnum::THREE);
+#endif
+}
diff -Nru pybind11-3.0.1/tests/pure_cpp/CMakeLists.txt pybind11-3.0.4/tests/pure_cpp/CMakeLists.txt
--- pybind11-3.0.1/tests/pure_cpp/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/pure_cpp/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -15,6 +15,8 @@
 add_custom_target(
   test_pure_cpp
   COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
-  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+  USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
+)
 
 add_dependencies(check test_pure_cpp)
diff -Nru pybind11-3.0.1/tests/pybind11_cross_module_tests.cpp pybind11-3.0.4/tests/pybind11_cross_module_tests.cpp
--- pybind11-3.0.1/tests/pybind11_cross_module_tests.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/pybind11_cross_module_tests.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -16,6 +16,15 @@
 #include <numeric>
 #include <utility>
 
+class CrossDSOClass {
+public:
+    CrossDSOClass() = default;
+    virtual ~CrossDSOClass();
+    CrossDSOClass(const CrossDSOClass &) = default;
+};
+
+CrossDSOClass::~CrossDSOClass() = default;
+
 PYBIND11_MODULE(pybind11_cross_module_tests, m, py::mod_gil_not_used()) {
     m.doc() = "pybind11 cross-module test module";
 
@@ -26,7 +35,9 @@
 
     // test_load_external
     bind_local<ExternalType1>(m, "ExternalType1", py::module_local());
-    bind_local<ExternalType2>(m, "ExternalType2", py::module_local());
+    bind_local<ExternalType2, 0, std::shared_ptr<ExternalType2>>(
+        m, "ExternalType2", py::module_local());
+    bind_local<ExternalType3, 0, py::smart_holder>(m, "ExternalType3", py::module_local());
 
     // test_exceptions.py
     py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
@@ -146,4 +157,7 @@
     // which appears when this header is missing.
     m.def("missing_header_arg", [](const std::vector<float> &) {});
     m.def("missing_header_return", []() { return std::vector<float>(); });
+
+    // test_class_cross_module_use_after_one_module_dealloc
+    m.def("consume_cross_dso_class", [](const CrossDSOClass &) {});
 }
diff -Nru pybind11-3.0.1/tests/pybind11_tests.cpp pybind11-3.0.4/tests/pybind11_tests.cpp
--- pybind11-3.0.1/tests/pybind11_tests.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/pybind11_tests.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -96,6 +96,12 @@
 #else
         false;
 #endif
+    m.attr("PYBIND11_TEST_SMART_HOLDER") =
+#if defined(PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
+        true;
+#else
+        false;
+#endif
 
     bind_ConstructorStats(m);
 
@@ -105,6 +111,12 @@
     m.attr("detailed_error_messages_enabled") = false;
 #endif
 
+#if defined(__cpp_noexcept_function_type)
+    m.attr("defined___cpp_noexcept_function_type") = true;
+#else
+    m.attr("defined___cpp_noexcept_function_type") = false;
+#endif
+
     py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
         .def(py::init<>())
         .def(py::init<int>())
diff -Nru pybind11-3.0.1/tests/pyproject.toml pybind11-3.0.4/tests/pyproject.toml
--- pybind11-3.0.1/tests/pyproject.toml	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/pyproject.toml	2026-04-19 06:01:51.000000000 +0300
@@ -27,7 +27,9 @@
 
 [tool.cibuildwheel]
 test-sources = ["tests", "pyproject.toml"]
-test-command = "python -m pytest -o timeout=0 -p no:cacheprovider tests"
+test-command = "python -m pytest -v -o timeout=120 -p no:cacheprovider tests"
+# Pyodide doesn't have signal.setitimer, so pytest-timeout can't work with timeout > 0
+pyodide.test-command = "python -m pytest -v -o timeout=0 -p no:cacheprovider tests"
 environment.PIP_ONLY_BINARY = "numpy"
 environment.PIP_PREFER_BINARY = "1"
 
diff -Nru pybind11-3.0.1/tests/pytest.ini pybind11-3.0.4/tests/pytest.ini
--- pybind11-3.0.1/tests/pytest.ini	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/pytest.ini	2026-04-19 06:01:51.000000000 +0300
@@ -9,7 +9,7 @@
     --capture=sys
     # Show local info when a failure occurs
     --showlocals
-log_cli_level = info
+log_level = INFO
 filterwarnings =
     # make warnings into errors but ignore certain third-party extension issues
     error
diff -Nru pybind11-3.0.1/tests/requirements.txt pybind11-3.0.4/tests/requirements.txt
--- pybind11-3.0.1/tests/requirements.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/requirements.txt	2026-04-19 06:01:51.000000000 +0300
@@ -7,8 +7,10 @@
 numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux"
 numpy~=1.21.5; platform_python_implementation=="CPython" and python_version>="3.8" and python_version<"3.10"
 numpy~=1.22.2; platform_python_implementation=="CPython" and python_version=="3.10"
-numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13"
-numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13"
+numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13" and platform_machine!="ARM64"
+numpy>=2.3.0; platform_python_implementation=="CPython" and python_version>="3.11" and platform_machine=="ARM64"
+numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13" and platform_machine!="ARM64"
+numpy>=2.4.0; platform_python_implementation=="CPython" and python_version>="3.14"
 pytest>=6
 pytest-timeout
 scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10"
diff -Nru pybind11-3.0.1/tests/standalone_enum_module.cpp pybind11-3.0.4/tests/standalone_enum_module.cpp
--- pybind11-3.0.1/tests/standalone_enum_module.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/standalone_enum_module.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 The pybind Community.
+
+#include <pybind11/pybind11.h>
+
+namespace standalone_enum_module_ns {
+enum SomeEnum {};
+} // namespace standalone_enum_module_ns
+
+using namespace standalone_enum_module_ns;
+
+PYBIND11_MODULE(standalone_enum_module, m) { // Added in PR #6015
+    pybind11::enum_<SomeEnum> some_enum_wrapper(m, "SomeEnum");
+}
diff -Nru pybind11-3.0.1/tests/test_buffers.cpp pybind11-3.0.4/tests/test_buffers.cpp
--- pybind11-3.0.1/tests/test_buffers.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_buffers.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -227,7 +227,7 @@
                               + std::to_string(cols) + "(*" + std::to_string(col_factor)
                               + ") matrix");
         }
-
+        DiscontiguousMatrix(const DiscontiguousMatrix &) = delete;
         ~DiscontiguousMatrix() {
             print_destroyed(this,
                             std::to_string(rows() / m_row_factor) + "(*"
@@ -237,11 +237,11 @@
         }
 
         float operator()(py::ssize_t i, py::ssize_t j) const {
-            return Matrix::operator()(i *m_row_factor, j *m_col_factor);
+            return Matrix::operator()(i * m_row_factor, j * m_col_factor);
         }
 
         float &operator()(py::ssize_t i, py::ssize_t j) {
-            return Matrix::operator()(i *m_row_factor, j *m_col_factor);
+            return Matrix::operator()(i * m_row_factor, j * m_col_factor);
         }
 
         using Matrix::data;
@@ -439,4 +439,133 @@
         PyBuffer_Release(&buffer);
         return result;
     });
+
+    // test_noexcept_def_buffer (issue #2234)
+    // def_buffer(Return (Class::*)(Args...) noexcept) and
+    // def_buffer(Return (Class::*)(Args...) const noexcept) must compile and work correctly.
+    struct OneDBuffer {
+        // Declare m_data before m_n to match initialiser list order below.
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBuffer(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBuffer() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) noexcept)
+        py::buffer_info get_buffer() noexcept {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)});
+        }
+    };
+
+    // non-const noexcept member function form
+    py::class_<OneDBuffer>(m, "OneDBuffer", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBuffer::get_buffer);
+
+    // const noexcept member function form (separate class to avoid ambiguity)
+    struct OneDBufferConst {
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBufferConst(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBufferConst() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) const noexcept)
+        py::buffer_info get_buffer() const noexcept {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)},
+                                   /*readonly=*/true);
+        }
+    };
+    py::class_<OneDBufferConst>(m, "OneDBufferConst", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBufferConst::get_buffer);
+
+    // test_ref_qualified_def_buffer
+    struct OneDBufferLRef {
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBufferLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBufferLRef() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) &)
+        py::buffer_info get_buffer() & {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)});
+        }
+    };
+    py::class_<OneDBufferLRef>(m, "OneDBufferLRef", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBufferLRef::get_buffer);
+
+    struct OneDBufferConstLRef {
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBufferConstLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBufferConstLRef() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) const &)
+        py::buffer_info get_buffer() const & {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)},
+                                   /*readonly=*/true);
+        }
+    };
+    py::class_<OneDBufferConstLRef>(m, "OneDBufferConstLRef", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBufferConstLRef::get_buffer);
+
+#ifdef __cpp_noexcept_function_type
+    struct OneDBufferLRefNoexcept {
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBufferLRefNoexcept(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBufferLRefNoexcept() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) & noexcept)
+        py::buffer_info get_buffer() & noexcept {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)});
+        }
+    };
+    py::class_<OneDBufferLRefNoexcept>(m, "OneDBufferLRefNoexcept", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBufferLRefNoexcept::get_buffer);
+
+    struct OneDBufferConstLRefNoexcept {
+        float *m_data;
+        py::ssize_t m_n;
+        explicit OneDBufferConstLRefNoexcept(py::ssize_t n)
+            : m_data(new float[(size_t) n]()), m_n(n) {}
+        ~OneDBufferConstLRefNoexcept() { delete[] m_data; }
+        // Exercises def_buffer(Return (Class::*)(Args...) const & noexcept)
+        py::buffer_info get_buffer() const & noexcept {
+            return py::buffer_info(m_data,
+                                   sizeof(float),
+                                   py::format_descriptor<float>::format(),
+                                   1,
+                                   {m_n},
+                                   {(py::ssize_t) sizeof(float)},
+                                   /*readonly=*/true);
+        }
+    };
+    py::class_<OneDBufferConstLRefNoexcept>(
+        m, "OneDBufferConstLRefNoexcept", py::buffer_protocol())
+        .def(py::init<py::ssize_t>())
+        .def_buffer(&OneDBufferConstLRefNoexcept::get_buffer);
+#endif
 }
diff -Nru pybind11-3.0.1/tests/test_buffers.py pybind11-3.0.4/tests/test_buffers.py
--- pybind11-3.0.1/tests/test_buffers.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_buffers.py	2026-04-19 06:01:51.000000000 +0300
@@ -7,7 +7,7 @@
 import pytest
 
 import env
-from pybind11_tests import ConstructorStats
+from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
 from pybind11_tests import buffers as m
 
 np = pytest.importorskip("numpy")
@@ -399,3 +399,73 @@
         m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
     with pytest.raises(expected_exception):
         m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)
+
+
+def test_noexcept_def_buffer():
+    """Test issue #2234: def_buffer with noexcept member function pointers.
+
+    Covers both new def_buffer specialisations:
+      - def_buffer(Return (Class::*)(Args...) noexcept)
+      - def_buffer(Return (Class::*)(Args...) const noexcept)
+    """
+    # non-const noexcept member function form
+    buf = m.OneDBuffer(5)
+    arr = np.frombuffer(buf, dtype=np.float32)
+    assert arr.shape == (5,)
+    arr[2] = 3.14
+    arr2 = np.frombuffer(buf, dtype=np.float32)
+    assert arr2[2] == pytest.approx(3.14)
+
+    # const noexcept member function form
+    cbuf = m.OneDBufferConst(4)
+    carr = np.frombuffer(cbuf, dtype=np.float32)
+    assert carr.shape == (4,)
+    assert carr.flags["WRITEABLE"] is False
+
+
+def test_ref_qualified_def_buffer():
+    """Test issue #2234 follow-up: def_buffer with ref-qualified member function pointers.
+
+    Covers:
+      - def_buffer(Return (Class::*)(Args...) &)
+      - def_buffer(Return (Class::*)(Args...) const &)
+      - def_buffer(Return (Class::*)(Args...) & noexcept)
+      - def_buffer(Return (Class::*)(Args...) const & noexcept)
+    """
+    # non-const lvalue ref-qualified member function form
+    buf = m.OneDBufferLRef(5)
+    arr = np.frombuffer(buf, dtype=np.float32)
+    assert arr.shape == (5,)
+    arr[1] = 2.5
+    arr2 = np.frombuffer(buf, dtype=np.float32)
+    assert arr2[1] == pytest.approx(2.5)
+
+    # const lvalue ref-qualified member function form
+    cbuf = m.OneDBufferConstLRef(4)
+    carr = np.frombuffer(cbuf, dtype=np.float32)
+    assert carr.shape == (4,)
+    assert carr.flags["WRITEABLE"] is False
+
+
+@pytest.mark.skipif(
+    not defined___cpp_noexcept_function_type,
+    reason="Requires __cpp_noexcept_function_type",
+)
+def test_ref_qualified_noexcept_def_buffer():
+    """Test issue #2234 follow-up: def_buffer with noexcept ref-qualified member pointers.
+
+    Covers:
+      - def_buffer(Return (Class::*)(Args...) & noexcept)
+      - def_buffer(Return (Class::*)(Args...) const & noexcept)
+    """
+    nbuf = m.OneDBufferLRefNoexcept(3)
+    narr = np.frombuffer(nbuf, dtype=np.float32)
+    assert narr.shape == (3,)
+    narr[2] = 7.0
+    narr2 = np.frombuffer(nbuf, dtype=np.float32)
+    assert narr2[2] == pytest.approx(7.0)
+
+    ncbuf = m.OneDBufferConstLRefNoexcept(2)
+    ncarr = np.frombuffer(ncbuf, dtype=np.float32)
+    assert ncarr.shape == (2,)
+    assert ncarr.flags["WRITEABLE"] is False
diff -Nru pybind11-3.0.1/tests/test_builtin_casters.cpp pybind11-3.0.4/tests/test_builtin_casters.cpp
--- pybind11-3.0.1/tests/test_builtin_casters.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_builtin_casters.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -363,9 +363,11 @@
     m.def("complex_cast", [](float x) { return "{}"_s.format(x); });
     m.def("complex_cast",
           [](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
+    m.def("complex_convert", [](std::complex<float> x) { return x; });
+    m.def("complex_noconvert", [](std::complex<float> x) { return x; }, py::arg{}.noconvert());
 
     // test int vs. long (Python 2)
-    m.def("int_cast", []() { return (int) 42; });
+    m.def("int_cast", []() { return 42; });
     m.def("long_cast", []() { return (long) 42; });
     m.def("longlong_cast", []() { return ULLONG_MAX; });
 
diff -Nru pybind11-3.0.1/tests/test_builtin_casters.py pybind11-3.0.4/tests/test_builtin_casters.py
--- pybind11-3.0.1/tests/test_builtin_casters.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_builtin_casters.py	2026-04-19 06:01:51.000000000 +0300
@@ -286,7 +286,10 @@
 
     convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
 
-    assert doc(convert) == "int_passthrough(arg0: typing.SupportsInt) -> int"
+    assert (
+        doc(convert)
+        == "int_passthrough(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
+    )
     assert doc(noconvert) == "int_passthrough_noconvert(arg0: int) -> int"
 
     def requires_conversion(v):
@@ -301,7 +304,7 @@
     # TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
     # TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
     if sys.version_info < (3, 10) and env.CPYTHON:
-        with env.deprecated_call():
+        with pytest.deprecated_call():
             assert convert(Int()) == 42
     else:
         assert convert(Int()) == 42
@@ -322,19 +325,39 @@
 
 
 def test_float_convert(doc):
+    class Int:
+        def __int__(self):
+            return -5
+
+    class Index:
+        def __index__(self) -> int:
+            return -7
+
     class Float:
         def __float__(self):
             return 41.45
 
     convert, noconvert = m.float_passthrough, m.float_passthrough_noconvert
-    assert doc(convert) == "float_passthrough(arg0: typing.SupportsFloat) -> float"
+    assert (
+        doc(convert)
+        == "float_passthrough(arg0: typing.SupportsFloat | typing.SupportsIndex) -> float"
+    )
     assert doc(noconvert) == "float_passthrough_noconvert(arg0: float) -> float"
 
     def requires_conversion(v):
         pytest.raises(TypeError, noconvert, v)
 
+    def cant_convert(v):
+        pytest.raises(TypeError, convert, v)
+
     requires_conversion(Float())
+    requires_conversion(Index())
     assert pytest.approx(convert(Float())) == 41.45
+    assert pytest.approx(convert(Index())) == -7.0
+    assert isinstance(convert(Float()), float)
+    assert pytest.approx(convert(3)) == 3.0
+    requires_conversion(3)
+    cant_convert(Int())
 
 
 def test_numpy_int_convert():
@@ -354,7 +377,7 @@
     # TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
     # https://github.com/pybind/pybind11/issues/3408
     if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
-        with env.deprecated_call():
+        with pytest.deprecated_call():
             assert convert(np.float32(3.14159)) == 3
     else:
         assert convert(np.float32(3.14159)) == 3
@@ -381,7 +404,7 @@
     assert (
         doc(m.tuple_passthrough)
         == """
-        tuple_passthrough(arg0: tuple[bool, str, typing.SupportsInt]) -> tuple[int, str, bool]
+        tuple_passthrough(arg0: tuple[bool, str, typing.SupportsInt | typing.SupportsIndex]) -> tuple[int, str, bool]
 
         Return a triple in reversed order
     """
@@ -458,11 +481,61 @@
     assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]
 
 
-def test_complex_cast():
+def test_complex_cast(doc):
     """std::complex casts"""
+
+    class Complex:
+        def __complex__(self) -> complex:
+            return complex(5, 4)
+
+    class Float:
+        def __float__(self) -> float:
+            return 5.0
+
+    class Int:
+        def __int__(self) -> int:
+            return 3
+
+    class Index:
+        def __index__(self) -> int:
+            return 1
+
     assert m.complex_cast(1) == "1.0"
+    assert m.complex_cast(1.0) == "1.0"
+    assert m.complex_cast(Complex()) == "(5.0, 4.0)"
     assert m.complex_cast(2j) == "(0.0, 2.0)"
 
+    convert, noconvert = m.complex_convert, m.complex_noconvert
+
+    def requires_conversion(v):
+        pytest.raises(TypeError, noconvert, v)
+
+    def cant_convert(v):
+        pytest.raises(TypeError, convert, v)
+
+    assert (
+        doc(convert)
+        == "complex_convert(arg0: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex) -> complex"
+    )
+    assert doc(noconvert) == "complex_noconvert(arg0: complex) -> complex"
+
+    assert convert(1) == 1.0
+    assert convert(2.0) == 2.0
+    assert convert(1 + 5j) == 1.0 + 5.0j
+    assert convert(Complex()) == 5.0 + 4j
+    assert convert(Float()) == 5.0
+    assert isinstance(convert(Float()), complex)
+    cant_convert(Int())
+    assert convert(Index()) == 1
+    assert isinstance(convert(Index()), complex)
+
+    requires_conversion(1)
+    requires_conversion(2.0)
+    assert noconvert(1 + 5j) == 1.0 + 5.0j
+    requires_conversion(Complex())
+    requires_conversion(Float())
+    requires_conversion(Index())
+
 
 def test_bool_caster():
     """Test bool caster implicit conversions."""
diff -Nru pybind11-3.0.1/tests/test_callbacks.py pybind11-3.0.4/tests/test_callbacks.py
--- pybind11-3.0.1/tests/test_callbacks.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_callbacks.py	2026-04-19 06:01:51.000000000 +0300
@@ -140,11 +140,11 @@
 def test_function_signatures(doc):
     assert (
         doc(m.test_callback3)
-        == "test_callback3(arg0: collections.abc.Callable[[typing.SupportsInt], int]) -> str"
+        == "test_callback3(arg0: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex], int]) -> str"
     )
     assert (
         doc(m.test_callback4)
-        == "test_callback4() -> collections.abc.Callable[[typing.SupportsInt], int]"
+        == "test_callback4() -> collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex], int]"
     )
 
 
@@ -179,10 +179,11 @@
     # do some work async
     work = [1, 2, 3, 4]
     m.test_async_callback(gen_f(), work)
-    # wait until work is done
-    from time import sleep
-
-    sleep(0.5)
+    # Wait for all detached worker threads to finish.
+    deadline = time.monotonic() + 5.0
+    while len(res) < len(work) and time.monotonic() < deadline:
+        time.sleep(0.01)
+    assert len(res) == len(work), f"Timed out waiting for callbacks: res={res!r}"
     assert sum(res) == sum(x + 3 for x in work)
 
 
diff -Nru pybind11-3.0.1/tests/test_class.cpp pybind11-3.0.4/tests/test_class.cpp
--- pybind11-3.0.1/tests/test_class.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -58,6 +58,13 @@
 class Args : public py::args {};
 } // namespace pr5396_forward_declared_class
 
+struct ConvertibleFromAnything {
+    ConvertibleFromAnything() = default;
+    template <class T>
+    // NOLINTNEXTLINE(bugprone-forwarding-reference-overload,google-explicit-constructor)
+    ConvertibleFromAnything(T &&) {}
+};
+
 } // namespace test_class
 
 static_assert(py::detail::is_same_or_base_of<py::args, py::args>::value, "");
@@ -97,6 +104,10 @@
         ~NoConstructorNew() { print_destroyed(this); }
     };
 
+    struct DynamicAttr {
+        DynamicAttr() = default;
+    };
+
     py::class_<NoConstructor>(m, "NoConstructor")
         .def_static("new_instance", &NoConstructor::new_instance, "Return an instance");
 
@@ -105,6 +116,8 @@
         .def_static("__new__",
                     [](const py::object &) { return NoConstructorNew::new_instance(); });
 
+    py::class_<DynamicAttr>(m, "DynamicAttr", py::dynamic_attr()).def(py::init<>());
+
     // test_pass_unique_ptr
     struct ToBeHeldByUniquePtr {};
     py::class_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr")
@@ -521,6 +534,7 @@
     // test_exception_rvalue_abort
     struct PyPrintDestructor {
         PyPrintDestructor() = default;
+        PyPrintDestructor(const PyPrintDestructor &) = default;
         ~PyPrintDestructor() { py::print("Print from destructor"); }
         void throw_something() { throw std::runtime_error("error"); }
     };
@@ -577,6 +591,11 @@
     });
 
     test_class::pr4220_tripped_over_this::bind_empty0(m);
+
+    // Regression test for compiler error that showed up in #5866
+    m.def("return_universal_recipient", []() -> test_class::ConvertibleFromAnything {
+        return test_class::ConvertibleFromAnything{};
+    });
 }
 
 template <int N>
diff -Nru pybind11-3.0.1/tests/test_class_cross_module_use_after_one_module_dealloc.cpp pybind11-3.0.4/tests/test_class_cross_module_use_after_one_module_dealloc.cpp
--- pybind11-3.0.1/tests/test_class_cross_module_use_after_one_module_dealloc.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_class_cross_module_use_after_one_module_dealloc.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,23 @@
+#include "pybind11_tests.h"
+
+#include <iostream>
+
+class CrossDSOClass {
+public:
+    CrossDSOClass() = default;
+    virtual ~CrossDSOClass();
+    CrossDSOClass(const CrossDSOClass &) = default;
+};
+
+CrossDSOClass::~CrossDSOClass() = default;
+
+struct UnrelatedClass {};
+
+TEST_SUBMODULE(class_cross_module_use_after_one_module_dealloc, m) {
+    m.def("register_and_instantiate_cross_dso_class", [](const py::module_ &m) {
+        py::class_<CrossDSOClass>(m, "CrossDSOClass").def(py::init<>());
+        return CrossDSOClass();
+    });
+    m.def("register_unrelated_class",
+          [](const py::module_ &m) { py::class_<UnrelatedClass>(m, "UnrelatedClass"); });
+}
diff -Nru pybind11-3.0.1/tests/test_class_cross_module_use_after_one_module_dealloc.py pybind11-3.0.4/tests/test_class_cross_module_use_after_one_module_dealloc.py
--- pybind11-3.0.1/tests/test_class_cross_module_use_after_one_module_dealloc.py	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_class_cross_module_use_after_one_module_dealloc.py	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+import types
+
+import pytest
+
+import env
+from pybind11_tests import class_cross_module_use_after_one_module_dealloc as m
+
+
+@pytest.mark.skipif(
+    env.TYPES_ARE_IMMORTAL, reason="can't GC type objects on this platform"
+)
+def test_cross_module_use_after_one_module_dealloc():
+    # This is a regression test for a bug that occurred during development of
+    # internals::registered_types_cpp_fast (see #5842). registered_types_cpp_fast maps
+    # &typeid(T) to a raw non-owning pointer to a Python type object. If two DSOs both
+    # look up the same global type, they will create two separate entries in
+    # registered_types_cpp_fast, which will look like:
+    # +=========================================+
+    # |&typeid(T) from DSO 1|type object pointer|
+    # |&typeid(T) from DSO 2|type object pointer|
+    # +=========================================+
+    #
+    # Then, if the type object is destroyed and we don't take extra steps to clean up
+    # the table thoroughly, the first row of the table will be cleaned up but the second
+    # one will contain a dangling pointer to the old type object. Further lookups from
+    # DSO 2 will then return that dangling pointer, which will cause use-after-frees.
+
+    import pybind11_cross_module_tests as cm
+
+    module_scope = types.ModuleType("module_scope")
+    instance = m.register_and_instantiate_cross_dso_class(module_scope)
+    cm.consume_cross_dso_class(instance)
+
+    del instance
+    pytest.delattr_and_ensure_destroyed((module_scope, "CrossDSOClass"))
+
+    # Make sure that CrossDSOClass gets allocated at a different address.
+    m.register_unrelated_class(module_scope)
+
+    instance = m.register_and_instantiate_cross_dso_class(module_scope)
+    cm.consume_cross_dso_class(instance)
diff -Nru pybind11-3.0.1/tests/test_class.py pybind11-3.0.4/tests/test_class.py
--- pybind11-3.0.1/tests/test_class.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class.py	2026-04-19 06:01:51.000000000 +0300
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import gc
 import sys
 from unittest import mock
 
@@ -18,6 +19,13 @@
     return sys.getrefcount(ob)
 
 
+MANAGED_DICT_GET_REFERRERS_SUPPORTED = (
+    env.CPYTHON
+    and sys.version_info >= (3, 13, 13)
+    and (sys.version_info < (3, 14) or sys.version_info >= (3, 14, 4))
+)
+
+
 def test_obj_class_name():
     expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
     assert m.obj_class_name(UserType(1)) == expected_name
@@ -45,6 +53,16 @@
     assert cstats.alive() == 0
 
 
+@pytest.mark.skipif(
+    not MANAGED_DICT_GET_REFERRERS_SUPPORTED,
+    reason="Requires CPython 3.13.13+ or 3.14.4+ managed dict traversal support",
+)
+def test_get_referrers():
+    instance = m.DynamicAttr()
+    instance.a = "test"
+    assert instance in gc.get_referrers(instance.__dict__)
+
+
 def test_instance_new():
     instance = m.NoConstructorNew()  # .__new__(m.NoConstructor.__class__)
 
@@ -163,13 +181,13 @@
     assert (
         doc(m.NestBase.Nested.fn)
         == """
-        fn(self: m.class_.NestBase.Nested, arg0: typing.SupportsInt, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
+        fn(self: m.class_.NestBase.Nested, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
     """
     )
     assert (
         doc(m.NestBase.Nested.fa)
         == """
-        fa(self: m.class_.NestBase.Nested, a: typing.SupportsInt, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
+        fa(self: m.class_.NestBase.Nested, a: typing.SupportsInt | typing.SupportsIndex, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
     """
     )
     assert m.NestBase.__module__ == "pybind11_tests.class_"
diff -Nru pybind11-3.0.1/tests/test_class_release_gil_before_calling_cpp_dtor.cpp pybind11-3.0.4/tests/test_class_release_gil_before_calling_cpp_dtor.cpp
--- pybind11-3.0.1/tests/test_class_release_gil_before_calling_cpp_dtor.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_release_gil_before_calling_cpp_dtor.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -22,6 +22,7 @@
 
 public:
     explicit ProbeType(const std::string &unique_key) : unique_key{unique_key} {}
+    ProbeType(const ProbeType &) = default;
 
     ~ProbeType() {
         RegistryType &reg = PyGILState_Check_Results();
diff -Nru pybind11-3.0.1/tests/test_class_sh_basic.py pybind11-3.0.4/tests/test_class_sh_basic.py
--- pybind11-3.0.1/tests/test_class_sh_basic.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_basic.py	2026-04-19 06:01:51.000000000 +0300
@@ -112,8 +112,8 @@
         pass_f(obj)
     assert str(exc_info.value) == (
         "Missing value for wrapped C++ type"
-        + " `pybind11_tests::class_sh_basic::atyp`:"
-        + " Python instance was disowned."
+        " `pybind11_tests::class_sh_basic::atyp`:"
+        " Python instance was disowned."
     )
 
 
diff -Nru pybind11-3.0.1/tests/test_class_sh_mi_thunks.cpp pybind11-3.0.4/tests/test_class_sh_mi_thunks.cpp
--- pybind11-3.0.1/tests/test_class_sh_mi_thunks.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_mi_thunks.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -10,6 +10,8 @@
 // C++ vtables - Part 2 - Multiple Inheritance
 // ... the compiler creates a 'thunk' method that corrects `this` ...
 
+// This test was added under PR #4380
+
 struct Base0 {
     virtual ~Base0() = default;
     Base0() = default;
@@ -30,6 +32,110 @@
     Derived(const Derived &) = delete;
 };
 
+// ChatGPT-generated Diamond added under PR #5836
+
+struct VBase {
+    VBase() = default;
+    VBase(const VBase &) = default; // silence -Wdeprecated-copy-with-dtor
+    VBase &operator=(const VBase &) = default;
+    VBase(VBase &&) = default;
+    VBase &operator=(VBase &&) = default;
+    virtual ~VBase() = default;
+    virtual int ping() const { return 1; }
+    int vbase_tag = 42; // ensure it's not empty
+};
+
+// Make the virtual bases non-empty and (likely) differently sized.
+// The test does *not* require different sizes; we only want to avoid "all at offset 0".
+// If a compiler/ABI still places the virtual base at offset 0, our test logs that via
+// test_virtual_base_at_offset_0() and continues.
+struct Left : virtual VBase {
+    char pad_l[4]; // small, typically 4 + padding
+    ~Left() override = default;
+};
+struct Right : virtual VBase {
+    char pad_r[24]; // larger, to differ from Left
+    ~Right() override = default;
+};
+
+struct Diamond : Left, Right {
+    Diamond() = default;
+    Diamond(const Diamond &) = default;
+    ~Diamond() override = default;
+    int ping() const override { return 7; }
+    int self_tag = 99;
+};
+
+VBase *make_diamond_as_vbase_raw_ptr() {
+    auto *ptr = new Diamond;
+    return ptr; // upcast
+}
+
+std::shared_ptr<VBase> make_diamond_as_vbase_shared_ptr() {
+    auto shptr = std::make_shared<Diamond>();
+    return shptr; // upcast
+}
+
+std::unique_ptr<VBase> make_diamond_as_vbase_unique_ptr() {
+    auto uqptr = std::unique_ptr<Diamond>(new Diamond);
+    return uqptr; // upcast
+}
+
+// For diagnostics
+struct DiamondAddrs {
+    uintptr_t as_self;
+    uintptr_t as_vbase;
+    uintptr_t as_left;
+    uintptr_t as_right;
+};
+
+DiamondAddrs diamond_addrs() {
+    auto sp = std::make_shared<Diamond>();
+    return DiamondAddrs{reinterpret_cast<uintptr_t>(sp.get()),
+                        reinterpret_cast<uintptr_t>(static_cast<VBase *>(sp.get())),
+                        reinterpret_cast<uintptr_t>(static_cast<Left *>(sp.get())),
+                        reinterpret_cast<uintptr_t>(static_cast<Right *>(sp.get()))};
+}
+
+// Animal-Cat-Tiger reproducer copied from PR #5796
+// clone_raw_ptr, clone_unique_ptr added under PR #5836
+
+class Animal {
+public:
+    Animal() = default;
+    Animal(const Animal &) = default;
+    Animal &operator=(const Animal &) = default;
+    virtual Animal *clone_raw_ptr() const = 0;
+    virtual std::shared_ptr<Animal> clone_shared_ptr() const = 0;
+    virtual std::unique_ptr<Animal> clone_unique_ptr() const = 0;
+    virtual ~Animal() = default;
+};
+
+class Cat : virtual public Animal {
+public:
+    Cat() = default;
+    Cat(const Cat &) = default;
+    Cat &operator=(const Cat &) = default;
+    ~Cat() override = default;
+};
+
+class Tiger : virtual public Cat {
+public:
+    Tiger() = default;
+    Tiger(const Tiger &) = default;
+    Tiger &operator=(const Tiger &) = default;
+    ~Tiger() override = default;
+    Animal *clone_raw_ptr() const override {
+        return new Tiger(*this); // upcast
+    }
+    std::shared_ptr<Animal> clone_shared_ptr() const override {
+        return std::make_shared<Tiger>(*this); // upcast
+    }
+    std::unique_ptr<Animal> clone_unique_ptr() const override {
+        return std::unique_ptr<Tiger>(new Tiger(*this)); // upcast
+    }
+};
+
 } // namespace test_class_sh_mi_thunks
 
 TEST_SUBMODULE(class_sh_mi_thunks, m) {
@@ -90,4 +196,35 @@
         }
         return obj_der->vec.size();
     });
+
+    py::class_<VBase, py::smart_holder>(m, "VBase").def("ping", &VBase::ping);
+
+    py::class_<Left, VBase, py::smart_holder>(m, "Left");
+    py::class_<Right, VBase, py::smart_holder>(m, "Right");
+
+    py::class_<Diamond, Left, Right, py::smart_holder>(m, "Diamond", py::multiple_inheritance())
+        .def(py::init<>())
+        .def("ping", &Diamond::ping);
+
+    m.def("make_diamond_as_vbase_raw_ptr",
+          &make_diamond_as_vbase_raw_ptr,
+          py::return_value_policy::take_ownership);
+    m.def("make_diamond_as_vbase_shared_ptr", &make_diamond_as_vbase_shared_ptr);
+    m.def("make_diamond_as_vbase_unique_ptr", &make_diamond_as_vbase_unique_ptr);
+
+    py::class_<DiamondAddrs, py::smart_holder>(m, "DiamondAddrs")
+        .def_readonly("as_self", &DiamondAddrs::as_self)
+        .def_readonly("as_vbase", &DiamondAddrs::as_vbase)
+        .def_readonly("as_left", &DiamondAddrs::as_left)
+        .def_readonly("as_right", &DiamondAddrs::as_right);
+
+    m.def("diamond_addrs", &diamond_addrs);
+
+    py::classh<Animal>(m, "Animal");
+    py::classh<Cat, Animal>(m, "Cat");
+    py::classh<Tiger, Cat>(m, "Tiger", py::multiple_inheritance())
+        .def(py::init<>())
+        .def("clone_raw_ptr", &Tiger::clone_raw_ptr)
+        .def("clone_shared_ptr", &Tiger::clone_shared_ptr)
+        .def("clone_unique_ptr", &Tiger::clone_unique_ptr);
 }
diff -Nru pybind11-3.0.1/tests/test_class_sh_mi_thunks.py pybind11-3.0.4/tests/test_class_sh_mi_thunks.py
--- pybind11-3.0.1/tests/test_class_sh_mi_thunks.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_mi_thunks.py	2026-04-19 06:01:51.000000000 +0300
@@ -51,3 +51,54 @@
     assert (
         str(exc_info.value) == "Cannot disown external shared_ptr (load_as_unique_ptr)."
     )
+
+
+def test_virtual_base_not_at_offset_0():
+    # This test ensures that the Diamond fixture actually exercises a non-zero
+    # virtual-base subobject offset on our supported platforms/ABIs.
+    #
+    # If this assert ever fails on some platform/toolchain, please adjust the
+    # C++ fixture so the virtual base is *not* at offset 0:
+    #   - Keep VBase non-empty.
+    #   - Make Left and Right non-empty and asymmetrically sized and, if
+    #     needed, nudge with a modest alignment.
+    #   - The goal is to achieve a non-zero address delta between `Diamond*`
+    #     and `static_cast<VBase*>(Diamond*)`.
+    #
+    # Rationale: certain smart_holder features are exercised only when the
+    # registered subobject address differs from the most-derived object start,
+    # so this check guards test efficacy across compilers.
+    addrs = m.diamond_addrs()
+    assert addrs.as_vbase - addrs.as_self != 0, (
+        "Diamond VBase at offset 0 on this platform; to ensure test efficacy, "
+        "tweak fixtures (VBase/Left/Right) to ensure non-zero subobject offset."
+    )
+
+
+@pytest.mark.parametrize(
+    "make_fn",
+    [
+        m.make_diamond_as_vbase_raw_ptr,  # exercises smart_holder::from_raw_ptr_take_ownership
+        m.make_diamond_as_vbase_shared_ptr,  # exercises smart_holder_from_shared_ptr
+        m.make_diamond_as_vbase_unique_ptr,  # exercises smart_holder_from_unique_ptr
+    ],
+)
+def test_make_diamond_as_vbase(make_fn):
+    # Added under PR #5836
+    vb = make_fn()
+    assert vb.ping() == 7
+
+
+@pytest.mark.parametrize(
+    "clone_fn",
+    [
+        m.Tiger.clone_raw_ptr,
+        m.Tiger.clone_shared_ptr,
+        m.Tiger.clone_unique_ptr,
+    ],
+)
+def test_animal_cat_tiger(clone_fn):
+    # Based on Animal-Cat-Tiger reproducer under PR #5796
+    tiger = m.Tiger()
+    cloned = clone_fn(tiger)
+    assert isinstance(cloned, m.Tiger)
diff -Nru pybind11-3.0.1/tests/test_class_sh_property.cpp pybind11-3.0.4/tests/test_class_sh_property.cpp
--- pybind11-3.0.1/tests/test_class_sh_property.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_property.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -43,6 +43,24 @@
     const char *const_char_ptr_member = "ConstChar*";
 };
 
+// See PR #6008
+enum class EnumAB {
+    A = 0,
+    B = 1,
+};
+
+struct ShWithEnumABMember {
+    EnumAB level = EnumAB::A;
+};
+
+struct SimpleStruct {
+    int value = 7;
+};
+
+struct ShWithSimpleStructMember {
+    SimpleStruct legacy;
+};
+
 } // namespace test_class_sh_property
 
 TEST_SUBMODULE(class_sh_property, m) {
@@ -91,4 +109,21 @@
     py::classh<WithConstCharPtrMember>(m, "WithConstCharPtrMember")
         .def(py::init<>())
         .def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member);
+
+    // See PR #6008
+    py::enum_<EnumAB>(m, "EnumAB").value("A", EnumAB::A).value("B", EnumAB::B);
+
+    py::classh<ShWithEnumABMember>(m, "ShWithEnumABMember")
+        .def(py::init<>())
+        .def_readwrite("level", &ShWithEnumABMember::level);
+
+    py::class_<SimpleStruct>(m, "SimpleStruct")
+        .def(py::init<>())
+        .def_readwrite("value", &SimpleStruct::value);
+
+    py::classh<ShWithSimpleStructMember>(m, "ShWithSimpleStructMember")
+        .def(py::init<>())
+        .def_readwrite("legacy", &ShWithSimpleStructMember::legacy);
+
+    m.def("getSimpleStructAsShared", []() { return std::make_shared<SimpleStruct>(); });
 }
diff -Nru pybind11-3.0.1/tests/test_class_sh_property_non_owning.cpp pybind11-3.0.4/tests/test_class_sh_property_non_owning.cpp
--- pybind11-3.0.1/tests/test_class_sh_property_non_owning.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_property_non_owning.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -33,6 +33,8 @@
         }
     }
 
+    DataFieldsHolder(DataFieldsHolder &&) noexcept = default;
+
     DataField *vec_at(std::size_t index) {
         if (index >= vec.size()) {
             return nullptr;
diff -Nru pybind11-3.0.1/tests/test_class_sh_property.py pybind11-3.0.4/tests/test_class_sh_property.py
--- pybind11-3.0.1/tests/test_class_sh_property.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_property.py	2026-04-19 06:01:51.000000000 +0300
@@ -5,6 +5,7 @@
 import pytest
 
 import env  # noqa: F401
+import pybind11_tests
 from pybind11_tests import class_sh_property as m
 
 
@@ -164,3 +165,54 @@
 def test_readonly_const_char_ptr_member():
     obj = m.WithConstCharPtrMember()
     assert obj.const_char_ptr_member == "ConstChar*"
+
+
+# See PR #6008
+def test_enum_member_with_smart_holder_def_readwrite():
+    obj = m.ShWithEnumABMember()
+    assert obj.level == m.EnumAB.A
+    for _ in range(100):
+        v = obj.level
+        assert v == m.EnumAB.A
+        del v
+
+
+# See PR #6008
+def test_non_smart_holder_member_type_with_smart_holder_owner():
+    obj = m.ShWithSimpleStructMember()
+    for _ in range(1000):
+        v = obj.legacy
+        assert v.value == 7
+        del v
+
+
+# See PR #6008, previously this was UB
+@pytest.mark.skipif(
+    pybind11_tests.PYBIND11_TEST_SMART_HOLDER,
+    reason="PYBIND11_TEST_SMART_HOLDER changes the default holder",
+)
+def test_shared_ptr_return_for_unique_ptr_holder():
+    with pytest.raises(
+        RuntimeError,
+        match="Unable to convert std::shared_ptr<T> to Python when the bound type does not use std::shared_ptr or py::smart_holder as its holder type",
+    ):
+        m.getSimpleStructAsShared()
+
+
+def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member():
+    obj = m.ShWithSimpleStructMember()
+    legacy = obj.legacy
+    legacy.value = 13
+    assert obj.legacy.value == 13
+
+
+def test_non_smart_holder_member_type_with_smart_holder_owner_aliases_member_multiple_reads():
+    obj = m.ShWithSimpleStructMember()
+
+    a = obj.legacy
+    b = obj.legacy
+
+    a.value = 13
+
+    assert b.value == 13
+    assert obj.legacy.value == 13
diff -Nru pybind11-3.0.1/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp pybind11-3.0.4/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
--- pybind11-3.0.1/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -36,9 +36,9 @@
 struct SpBaseTester {
     std::shared_ptr<SpBase> get_object() const { return m_obj; }
     void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
-    bool is_base_used() { return m_obj->is_base_used(); }
-    bool has_instance() { return (bool) m_obj; }
-    bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
+    bool is_base_used() const { return m_obj->is_base_used(); }
+    bool has_instance() const { return (bool) m_obj; }
+    bool has_python_instance() const { return m_obj && m_obj->has_python_instance(); }
     void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
     std::shared_ptr<SpBase> m_obj;
 };
diff -Nru pybind11-3.0.1/tests/test_cmake_build/installed_embed/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/installed_embed/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/installed_embed/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/installed_embed/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_installed_embed CXX)
 
diff -Nru pybind11-3.0.1/tests/test_cmake_build/installed_function/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/installed_function/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/installed_function/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/installed_function/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_installed_function CXX)
 
diff -Nru pybind11-3.0.1/tests/test_cmake_build/installed_target/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/installed_target/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/installed_target/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/installed_target/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_installed_target CXX)
 
diff -Nru pybind11-3.0.1/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_subdirectory_embed CXX)
 
@@ -26,11 +26,11 @@
   DEPENDS test_subdirectory_embed)
 
 # Test custom export group -- PYBIND11_EXPORT_NAME
-add_library(test_embed_lib ../embed.cpp)
-target_link_libraries(test_embed_lib PRIVATE pybind11::embed)
+add_library(test_with_catch_lib ../embed.cpp)
+target_link_libraries(test_with_catch_lib PRIVATE pybind11::embed)
 
 install(
-  TARGETS test_embed_lib
+  TARGETS test_with_catch_lib
   EXPORT test_export
   ARCHIVE DESTINATION bin
   LIBRARY DESTINATION lib
diff -Nru pybind11-3.0.1/tests/test_cmake_build/subdirectory_function/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/subdirectory_function/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/subdirectory_function/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/subdirectory_function/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_subdirectory_function CXX)
 
diff -Nru pybind11-3.0.1/tests/test_cmake_build/subdirectory_target/CMakeLists.txt pybind11-3.0.4/tests/test_cmake_build/subdirectory_target/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cmake_build/subdirectory_target/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cmake_build/subdirectory_target/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 project(test_subdirectory_target CXX)
 
diff -Nru pybind11-3.0.1/tests/test_cpp_conduit.py pybind11-3.0.4/tests/test_cpp_conduit.py
--- pybind11-3.0.1/tests/test_cpp_conduit.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cpp_conduit.py	2026-04-19 06:01:51.000000000 +0300
@@ -12,7 +12,7 @@
 
 
 def import_warns_freethreaded(name):
-    if name not in sys.modules and not getattr(sys, "_is_gil_enabled", lambda: True)():
+    if name not in sys.modules and not env.sys_is_gil_enabled():
         with pytest.warns(
             RuntimeWarning, match=f"has been enabled to load module '{name}'"
         ):
diff -Nru pybind11-3.0.1/tests/test_cross_module_rtti/CMakeLists.txt pybind11-3.0.4/tests/test_cross_module_rtti/CMakeLists.txt
--- pybind11-3.0.1/tests/test_cross_module_rtti/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cross_module_rtti/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -60,7 +60,9 @@
   test_cross_module_rtti
   COMMAND "$<TARGET_FILE:test_cross_module_rtti_main>"
   DEPENDS test_cross_module_rtti_main
-  WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>")
+  WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>"
+  USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
+)
 
 set_target_properties(test_cross_module_rtti_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY
                                                                  "${CMAKE_CURRENT_BINARY_DIR}")
diff -Nru pybind11-3.0.1/tests/test_cross_module_rtti/lib.h pybind11-3.0.4/tests/test_cross_module_rtti/lib.h
--- pybind11-3.0.1/tests/test_cross_module_rtti/lib.h	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_cross_module_rtti/lib.h	2026-04-19 06:01:51.000000000 +0300
@@ -12,6 +12,7 @@
     class TEST_CROSS_MODULE_RTTI_LIB_EXPORT Base : public std::enable_shared_from_this<Base> {
     public:
         Base(int a, int b);
+        Base(const Base &) = default;
         virtual ~Base() = default;
 
         virtual int get() const;
diff -Nru pybind11-3.0.1/tests/test_custom_type_casters.py pybind11-3.0.4/tests/test_custom_type_casters.py
--- pybind11-3.0.1/tests/test_custom_type_casters.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_custom_type_casters.py	2026-04-19 06:01:51.000000000 +0300
@@ -75,7 +75,7 @@
         msg(excinfo.value)
         == """
         ints_preferred(): incompatible function arguments. The following argument types are supported:
-            1. (i: typing.SupportsInt) -> int
+            1. (i: typing.SupportsInt | typing.SupportsIndex) -> int
 
         Invoked with: 4.0
     """
diff -Nru pybind11-3.0.1/tests/test_custom_type_setup.cpp pybind11-3.0.4/tests/test_custom_type_setup.cpp
--- pybind11-3.0.1/tests/test_custom_type_setup.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_custom_type_setup.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -7,22 +7,67 @@
     BSD-style license that can be found in the LICENSE file.
 */
 
+#include <pybind11/detail/internals.h>
 #include <pybind11/pybind11.h>
 
 #include "pybind11_tests.h"
 
+#include <vector>
+
 namespace py = pybind11;
 
 namespace {
+struct ContainerOwnsPythonObjects {
+    std::vector<py::object> list;
 
-struct OwnsPythonObjects {
-    py::object value = py::none();
+    void append(const py::object &obj) { list.emplace_back(obj); }
+    py::object at(py::ssize_t index) const {
+        if (index >= size() || index < 0) {
+            throw py::index_error("Index out of range");
+        }
+        return list.at(py::size_t(index));
+    }
+    py::ssize_t size() const { return py::ssize_t_cast(list.size()); }
+    void clear() { list.clear(); }
 };
+
+void add_gc_checkers_with_weakrefs(const py::object &obj) {
+    py::handle global_capsule = py::detail::get_internals_capsule();
+    if (!global_capsule) {
+        throw std::runtime_error("No global internals capsule found");
+    }
+    (void) py::weakref(obj, py::cpp_function([global_capsule](py::handle weakref) -> void {
+                           py::handle current_global_capsule = py::detail::get_internals_capsule();
+                           if (!current_global_capsule.is(global_capsule)) {
+                               throw std::runtime_error(
+                                   "Global internals capsule was destroyed prematurely");
+                           }
+                           weakref.dec_ref();
+                       }))
+        .release();
+
+    py::handle local_capsule = py::detail::get_local_internals_capsule();
+    if (!local_capsule) {
+        throw std::runtime_error("No local internals capsule found");
+    }
+    (void) py::weakref(
+        obj, py::cpp_function([local_capsule](py::handle weakref) -> void {
+            py::handle current_local_capsule = py::detail::get_local_internals_capsule();
+            if (!current_local_capsule.is(local_capsule)) {
+                throw std::runtime_error("Local internals capsule was destroyed prematurely");
+            }
+            weakref.dec_ref();
+        }))
+        .release();
+}
 } // namespace
 
 TEST_SUBMODULE(custom_type_setup, m) {
-    py::class_<OwnsPythonObjects> cls(
-        m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
+    py::class_<ContainerOwnsPythonObjects> cls(
+        m,
+        "ContainerOwnsPythonObjects",
+        // Please review/update docs/advanced/classes.rst after making changes here.
+        py::custom_type_setup([](PyHeapTypeObject *heap_type) {
             auto *type = &heap_type->ht_type;
             type->tp_flags |= Py_TPFLAGS_HAVE_GC;
             type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
@@ -31,19 +76,29 @@
                 Py_VISIT(Py_TYPE(self_base));
 #endif
                 if (py::detail::is_holder_constructed(self_base)) {
-                    auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
-                    Py_VISIT(self.value.ptr());
+                    auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
+                    for (auto &item : self.list) {
+                        Py_VISIT(item.ptr());
+                    }
                 }
                 return 0;
             };
             type->tp_clear = [](PyObject *self_base) {
                 if (py::detail::is_holder_constructed(self_base)) {
-                    auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
-                    self.value = py::none();
+                    auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
+                    for (auto &item : self.list) {
+                        Py_CLEAR(item.ptr());
+                    }
+                    self.list.clear();
                 }
                 return 0;
             };
         }));
     cls.def(py::init<>());
-    cls.def_readwrite("value", &OwnsPythonObjects::value);
+    cls.def("append", &ContainerOwnsPythonObjects::append);
+    cls.def("at", &ContainerOwnsPythonObjects::at);
+    cls.def("size", &ContainerOwnsPythonObjects::size);
+    cls.def("clear", &ContainerOwnsPythonObjects::clear);
+
+    m.def("add_gc_checkers_with_weakrefs", &add_gc_checkers_with_weakrefs);
 }
diff -Nru pybind11-3.0.1/tests/test_custom_type_setup.py pybind11-3.0.4/tests/test_custom_type_setup.py
--- pybind11-3.0.1/tests/test_custom_type_setup.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_custom_type_setup.py	2026-04-19 06:01:51.000000000 +0300
@@ -1,11 +1,13 @@
 from __future__ import annotations
 
 import gc
+import os
 import weakref
 
 import pytest
 
-import env  # noqa: F401
+import env
+import pybind11_tests
 from pybind11_tests import custom_type_setup as m
 
 
@@ -36,15 +38,42 @@
 # PyPy does not seem to reliably garbage collect.
 @pytest.mark.skipif("env.PYPY or env.GRAALPY")
 def test_self_cycle(gc_tester):
-    obj = m.OwnsPythonObjects()
-    obj.value = obj
+    obj = m.ContainerOwnsPythonObjects()
+    obj.append(obj)
     gc_tester(obj)
 
 
 # PyPy does not seem to reliably garbage collect.
 @pytest.mark.skipif("env.PYPY or env.GRAALPY")
 def test_indirect_cycle(gc_tester):
-    obj = m.OwnsPythonObjects()
-    obj_list = [obj]
-    obj.value = obj_list
+    obj = m.ContainerOwnsPythonObjects()
+    obj.append([obj])
     gc_tester(obj)
+
+
+@pytest.mark.skipif("env.PYPY or env.GRAALPY")
+def test_py_cast_useable_on_shutdown():
+    """Test that py::cast works during interpreter shutdown.
+
+    See PR #5972 and https://github.com/pybind/pybind11/pull/5958#discussion_r2717645230.
+    """
+    env.check_script_success_in_subprocess(
+        f"""
+        import sys
+
+        sys.path.insert(0, {os.path.dirname(env.__file__)!r})
+        sys.path.insert(0, {os.path.dirname(pybind11_tests.__file__)!r})
+
+        from pybind11_tests import custom_type_setup as m
+
+        # Create a self-referential cycle that will be collected during shutdown.
+        # The tp_traverse and tp_clear callbacks call py::cast, which requires
+        # internals to still be valid.
+        obj = m.ContainerOwnsPythonObjects()
+        obj.append(obj)
+
+        # Add weakref callbacks that verify the capsule is still alive when the
+        # pybind11 object is garbage collected during shutdown.
+        m.add_gc_checkers_with_weakrefs(obj)
+        """
+    )
diff -Nru pybind11-3.0.1/tests/test_docstring_options.py pybind11-3.0.4/tests/test_docstring_options.py
--- pybind11-3.0.1/tests/test_docstring_options.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_docstring_options.py	2026-04-19 06:01:51.000000000 +0300
@@ -20,11 +20,11 @@
 
     # options.enable_function_signatures()
     assert m.test_function3.__doc__.startswith(
-        "test_function3(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
+        "test_function3(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
     )
 
     assert m.test_function4.__doc__.startswith(
-        "test_function4(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
+        "test_function4(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
     )
     assert m.test_function4.__doc__.endswith("A custom docstring\n")
 
@@ -37,7 +37,7 @@
 
     # RAII destructor
     assert m.test_function7.__doc__.startswith(
-        "test_function7(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
+        "test_function7(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
     )
     assert m.test_function7.__doc__.endswith("A custom docstring\n")
 
@@ -48,7 +48,7 @@
     assert not m.DocstringTestFoo.__doc__
     assert not m.DocstringTestFoo.value_prop.__doc__
 
-    # Check existig behaviour of enum docstings
+    # Check existing behaviour of enum docstings
     assert (
         m.DocstringTestEnum1.__doc__
         == "Enum docstring\n\nMembers:\n\n  Member1\n\n  Member2"
diff -Nru pybind11-3.0.1/tests/test_eigen_matrix.cpp pybind11-3.0.4/tests/test_eigen_matrix.cpp
--- pybind11-3.0.1/tests/test_eigen_matrix.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_eigen_matrix.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -237,6 +237,7 @@
 
     public:
         ReturnTester() { print_created(this); }
+        ReturnTester(const ReturnTester &) = default;
         ~ReturnTester() { print_destroyed(this); }
         static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); }
         // NOLINTNEXTLINE(readability-const-return-type)
diff -Nru pybind11-3.0.1/tests/test_embed/catch.cpp pybind11-3.0.4/tests/test_embed/catch.cpp
--- pybind11-3.0.1/tests/test_embed/catch.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/catch.cpp	1970-01-01 02:00:00.000000000 +0200
@@ -1,43 +0,0 @@
-// The Catch implementation is compiled here. This is a standalone
-// translation unit to avoid recompiling it for every test change.
-
-#include <pybind11/embed.h>
-
-// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
-// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
-PYBIND11_WARNING_DISABLE_MSVC(4996)
-
-// Catch uses _ internally, which breaks gettext style defines
-#ifdef _
-#    undef _
-#endif
-
-#define CATCH_CONFIG_RUNNER
-#include <catch.hpp>
-
-namespace py = pybind11;
-
-int main(int argc, char *argv[]) {
-    // Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number:
-    std::string updated_pythonpath("pybind11_test_embed_PYTHONPATH_2099743835476552");
-    const char *preexisting_pythonpath = getenv("PYTHONPATH");
-    if (preexisting_pythonpath != nullptr) {
-#if defined(_WIN32)
-        updated_pythonpath += ';';
-#else
-        updated_pythonpath += ':';
-#endif
-        updated_pythonpath += preexisting_pythonpath;
-    }
-#if defined(_WIN32)
-    _putenv_s("PYTHONPATH", updated_pythonpath.c_str());
-#else
-    setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1);
-#endif
-
-    py::scoped_interpreter guard{};
-
-    auto result = Catch::Session().run(argc, argv);
-
-    return result < 0xff ? result : 0xff;
-}
diff -Nru pybind11-3.0.1/tests/test_embed/CMakeLists.txt pybind11-3.0.4/tests/test_embed/CMakeLists.txt
--- pybind11-3.0.1/tests/test_embed/CMakeLists.txt	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/CMakeLists.txt	1970-01-01 02:00:00.000000000 +0200
@@ -1,61 +0,0 @@
-possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
-
-if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy"
-   OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
-   OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")
-  message(STATUS "Skipping embed test on PyPy or GraalPy")
-  add_custom_target(cpptest) # Dummy target on PyPy or GraalPy. Embedding is not supported.
-  set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
-  return()
-endif()
-
-if(TARGET Python::Module AND NOT TARGET Python::Python)
-  message(STATUS "Skipping embed test since no embed libs found")
-  add_custom_target(cpptest) # Dummy target since embedding is not supported.
-  set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
-  return()
-endif()
-
-find_package(Catch 2.13.10)
-
-if(CATCH_FOUND)
-  message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}")
-else()
-  message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
-                 " manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
-  return()
-endif()
-
-find_package(Threads REQUIRED)
-
-if(PYBIND11_TEST_SMART_HOLDER)
-  add_compile_definitions(
-    -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
-endif()
-
-add_executable(test_embed catch.cpp test_interpreter.cpp test_subinterpreter.cpp)
-pybind11_enable_warnings(test_embed)
-
-target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads)
-
-if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
-  file(COPY test_interpreter.py test_trampoline.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
-endif()
-
-add_custom_target(
-  cpptest
-  COMMAND "$<TARGET_FILE:test_embed>"
-  DEPENDS test_embed
-  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
-
-pybind11_add_module(external_module THIN_LTO external_module.cpp)
-set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY
-                                                 "${CMAKE_CURRENT_BINARY_DIR}")
-foreach(config ${CMAKE_CONFIGURATION_TYPES})
-  string(TOUPPER ${config} config)
-  set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
-                                                   "${CMAKE_CURRENT_BINARY_DIR}")
-endforeach()
-add_dependencies(cpptest external_module)
-
-add_dependencies(check cpptest)
diff -Nru pybind11-3.0.1/tests/test_embed/external_module.cpp pybind11-3.0.4/tests/test_embed/external_module.cpp
--- pybind11-3.0.1/tests/test_embed/external_module.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/external_module.cpp	1970-01-01 02:00:00.000000000 +0200
@@ -1,24 +0,0 @@
-#include <pybind11/pybind11.h>
-
-namespace py = pybind11;
-
-/* Simple test module/test class to check that the referenced internals data of external pybind11
- * modules aren't preserved over a finalize/initialize.
- */
-
-PYBIND11_MODULE(external_module,
-                m,
-                py::mod_gil_not_used(),
-                py::multiple_interpreters::per_interpreter_gil()) {
-
-    class A {
-    public:
-        explicit A(int value) : v{value} {};
-        int v;
-    };
-
-    py::class_<A>(m, "A").def(py::init<int>()).def_readwrite("value", &A::v);
-
-    m.def("internals_at",
-          []() { return reinterpret_cast<uintptr_t>(&py::detail::get_internals()); });
-}
diff -Nru pybind11-3.0.1/tests/test_embed/test_interpreter.cpp pybind11-3.0.4/tests/test_embed/test_interpreter.cpp
--- pybind11-3.0.1/tests/test_embed/test_interpreter.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/test_interpreter.cpp	1970-01-01 02:00:00.000000000 +0200
@@ -1,482 +0,0 @@
-#include <pybind11/critical_section.h>
-#include <pybind11/embed.h>
-
-// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
-// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
-PYBIND11_WARNING_DISABLE_MSVC(4996)
-
-#include <catch.hpp>
-#include <cstdlib>
-#include <fstream>
-#include <functional>
-#include <thread>
-#include <utility>
-
-namespace py = pybind11;
-using namespace py::literals;
-
-size_t get_sys_path_size() {
-    auto sys_path = py::module::import("sys").attr("path");
-    return py::len(sys_path);
-}
-
-bool has_state_dict_internals_obj() {
-    py::dict state = py::detail::get_python_state_dict();
-    return state.contains(PYBIND11_INTERNALS_ID);
-}
-
-uintptr_t get_details_as_uintptr() {
-    return reinterpret_cast<uintptr_t>(py::detail::get_internals_pp_manager().get_pp()->get());
-}
-
-class Widget {
-public:
-    explicit Widget(std::string message) : message(std::move(message)) {}
-    virtual ~Widget() = default;
-
-    std::string the_message() const { return message; }
-    virtual int the_answer() const = 0;
-    virtual std::string argv0() const = 0;
-
-private:
-    std::string message;
-};
-
-class PyWidget final : public Widget {
-    using Widget::Widget;
-
-    int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); }
-    std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); }
-};
-
-class test_override_cache_helper {
-
-public:
-    virtual int func() { return 0; }
-
-    test_override_cache_helper() = default;
-    virtual ~test_override_cache_helper() = default;
-    // Non-copyable
-    test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete;
-    test_override_cache_helper(test_override_cache_helper const &Copy) = delete;
-};
-
-class test_override_cache_helper_trampoline : public test_override_cache_helper {
-    int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
-};
-
-PYBIND11_EMBEDDED_MODULE(widget_module, m, py::multiple_interpreters::per_interpreter_gil()) {
-    py::class_<Widget, PyWidget>(m, "Widget")
-        .def(py::init<std::string>())
-        .def_property_readonly("the_message", &Widget::the_message);
-
-    m.def("add", [](int i, int j) { return i + j; });
-
-    auto sub = m.def_submodule("sub");
-    sub.def("add", [](int i, int j) { return i + j; });
-}
-
-PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
-    py::class_<test_override_cache_helper,
-               test_override_cache_helper_trampoline,
-               std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper")
-        .def(py::init_alias<>())
-        .def("func", &test_override_cache_helper::func);
-}
-
-PYBIND11_EMBEDDED_MODULE(throw_exception, ) { throw std::runtime_error("C++ Error"); }
-
-PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
-    auto d = py::dict();
-    d["missing"].cast<py::object>();
-}
-
-TEST_CASE("PYTHONPATH is used to update sys.path") {
-    // The setup for this TEST_CASE is in catch.cpp!
-    auto sys_path = py::str(py::module_::import("sys").attr("path")).cast<std::string>();
-    REQUIRE_THAT(sys_path,
-                 Catch::Matchers::Contains("pybind11_test_embed_PYTHONPATH_2099743835476552"));
-}
-
-TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
-    auto module_ = py::module_::import("test_interpreter");
-    REQUIRE(py::hasattr(module_, "DerivedWidget"));
-
-    auto locals = py::dict("hello"_a = "Hello, World!", "x"_a = 5, **module_.attr("__dict__"));
-    py::exec(R"(
-        widget = DerivedWidget("{} - {}".format(hello, x))
-        message = widget.the_message
-    )",
-             py::globals(),
-             locals);
-    REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5");
-
-    auto py_widget = module_.attr("DerivedWidget")("The question");
-    auto message = py_widget.attr("the_message");
-    REQUIRE(message.cast<std::string>() == "The question");
-
-    const auto &cpp_widget = py_widget.cast<const Widget &>();
-    REQUIRE(cpp_widget.the_answer() == 42);
-}
-
-TEST_CASE("Override cache") {
-    auto module_ = py::module_::import("test_trampoline");
-    REQUIRE(py::hasattr(module_, "func"));
-    REQUIRE(py::hasattr(module_, "func2"));
-
-    auto locals = py::dict(**module_.attr("__dict__"));
-
-    int i = 0;
-    for (; i < 1500; ++i) {
-        std::shared_ptr<test_override_cache_helper> p_obj;
-        std::shared_ptr<test_override_cache_helper> p_obj2;
-
-        py::object loc_inst = locals["func"]();
-        p_obj = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
-
-        int ret = p_obj->func();
-
-        REQUIRE(ret == 42);
-
-        loc_inst = locals["func2"]();
-
-        p_obj2 = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
-
-        p_obj2->func();
-    }
-}
-
-TEST_CASE("Import error handling") {
-    REQUIRE_NOTHROW(py::module_::import("widget_module"));
-    REQUIRE_THROWS_WITH(py::module_::import("throw_exception"), "ImportError: C++ Error");
-    REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
-                        Catch::Contains("ImportError: initialization failed"));
-
-    auto locals = py::dict("is_keyerror"_a = false, "message"_a = "not set");
-    py::exec(R"(
-        try:
-            import throw_error_already_set
-        except ImportError as e:
-            is_keyerror = type(e.__cause__) == KeyError
-            message = str(e.__cause__)
-    )",
-             py::globals(),
-             locals);
-    REQUIRE(locals["is_keyerror"].cast<bool>() == true);
-    REQUIRE(locals["message"].cast<std::string>() == "'missing'");
-}
-
-TEST_CASE("There can be only one interpreter") {
-    static_assert(std::is_move_constructible<py::scoped_interpreter>::value, "");
-    static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, "");
-    static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, "");
-    static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, "");
-
-    REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running");
-    REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running");
-
-    py::finalize_interpreter();
-    REQUIRE_NOTHROW(py::scoped_interpreter());
-    {
-        auto pyi1 = py::scoped_interpreter();
-        auto pyi2 = std::move(pyi1);
-    }
-    py::initialize_interpreter();
-}
-
-#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
-TEST_CASE("Custom PyConfig") {
-    py::finalize_interpreter();
-    PyConfig config;
-    PyConfig_InitPythonConfig(&config);
-    REQUIRE_NOTHROW(py::scoped_interpreter{&config});
-    {
-        py::scoped_interpreter p{&config};
-        REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast<int>() == 42);
-    }
-    py::initialize_interpreter();
-}
-
-TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") {
-    std::vector<std::string> path;
-    for (auto p : py::module::import("sys").attr("path")) {
-        path.emplace_back(py::str(p));
-    }
-
-    py::finalize_interpreter();
-    {
-        PyConfig config;
-        PyConfig_InitIsolatedConfig(&config);
-        char *argv[] = {strdup("a.out")};
-        py::scoped_interpreter argv_scope{&config, 1, argv, true};
-        std::free(argv[0]);
-        // Because this config is isolated, setting the path during init will not work, we have to
-        // set it manually.  If we don't set it, then we can't import "test_interpreter"
-        for (auto &&p : path) {
-            py::list(py::module::import("sys").attr("path")).append(p);
-        }
-        try {
-            auto module = py::module::import("test_interpreter");
-            auto py_widget = module.attr("DerivedWidget")("The question");
-            const auto &cpp_widget = py_widget.cast<const Widget &>();
-            REQUIRE(cpp_widget.argv0() == "a.out");
-        } catch (py::error_already_set &e) {
-            // catch here so that the exception doesn't escape the interpreter that owns it
-            FAIL(e.what());
-        }
-    }
-    py::initialize_interpreter();
-}
-
-TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") {
-    py::finalize_interpreter();
-    {
-        PyConfig config;
-        PyConfig_InitPythonConfig(&config);
-
-        // `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by
-        // changing it to `0`. This test exercises `scoped_interpreter` with the default config.
-        char *argv[] = {strdup("a.out"), strdup("arg1")};
-        py::scoped_interpreter argv_scope(&config, 2, argv);
-        std::free(argv[0]);
-        std::free(argv[1]);
-        auto module = py::module::import("test_interpreter");
-        auto py_widget = module.attr("DerivedWidget")("The question");
-        const auto &cpp_widget = py_widget.cast<const Widget &>();
-        REQUIRE(cpp_widget.argv0() == "arg1");
-    }
-    py::initialize_interpreter();
-}
-#endif
-
-TEST_CASE("Add program dir to path pre-PyConfig") {
-    py::finalize_interpreter();
-    size_t path_size_add_program_dir_to_path_false = 0;
-    {
-        py::scoped_interpreter scoped_interp{true, 0, nullptr, false};
-        path_size_add_program_dir_to_path_false = get_sys_path_size();
-    }
-    {
-        py::scoped_interpreter scoped_interp{};
-        REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
-    }
-    py::initialize_interpreter();
-}
-
-#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
-TEST_CASE("Add program dir to path using PyConfig") {
-    py::finalize_interpreter();
-    size_t path_size_add_program_dir_to_path_false = 0;
-    {
-        PyConfig config;
-        PyConfig_InitPythonConfig(&config);
-        py::scoped_interpreter scoped_interp{&config, 0, nullptr, false};
-        path_size_add_program_dir_to_path_false = get_sys_path_size();
-    }
-    {
-        PyConfig config;
-        PyConfig_InitPythonConfig(&config);
-        py::scoped_interpreter scoped_interp{&config};
-        REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
-    }
-    py::initialize_interpreter();
-}
-#endif
-
-TEST_CASE("Restart the interpreter") {
-    // Verify pre-restart state.
-    REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
-    REQUIRE(has_state_dict_internals_obj());
-    REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
-            == 123);
-
-    // local and foreign module internals should point to the same internals:
-    REQUIRE(get_details_as_uintptr()
-            == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
-
-    // Restart the interpreter.
-    py::finalize_interpreter();
-    REQUIRE(Py_IsInitialized() == 0);
-
-    py::initialize_interpreter();
-    REQUIRE(Py_IsInitialized() == 1);
-
-    // Internals are deleted after a restart.
-    REQUIRE_FALSE(has_state_dict_internals_obj());
-    REQUIRE(get_details_as_uintptr() == 0);
-    pybind11::detail::get_internals();
-    REQUIRE(has_state_dict_internals_obj());
-    REQUIRE(get_details_as_uintptr() != 0);
-    REQUIRE(get_details_as_uintptr()
-            == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
-
-    // Make sure that an interpreter with no get_internals() created until finalize still gets the
-    // internals destroyed
-    py::finalize_interpreter();
-    py::initialize_interpreter();
-    bool ran = false;
-    py::module_::import("__main__").attr("internals_destroy_test")
-        = py::capsule(&ran, [](void *ran) {
-              py::detail::get_internals();
-              REQUIRE(has_state_dict_internals_obj());
-              *static_cast<bool *>(ran) = true;
-          });
-    REQUIRE_FALSE(has_state_dict_internals_obj());
-    REQUIRE_FALSE(ran);
-    py::finalize_interpreter();
-    REQUIRE(ran);
-    py::initialize_interpreter();
-    REQUIRE_FALSE(has_state_dict_internals_obj());
-    REQUIRE(get_details_as_uintptr() == 0);
-
-    // C++ modules can be reloaded.
-    auto cpp_module = py::module_::import("widget_module");
-    REQUIRE(cpp_module.attr("add")(1, 2).cast<int>() == 3);
-
-    // Also verify submodules work
-    REQUIRE(cpp_module.attr("sub").attr("add")(1, 41).cast<int>() == 42);
-
-    // C++ type information is reloaded and can be used in python modules.
-    auto py_module = py::module_::import("test_interpreter");
-    auto py_widget = py_module.attr("DerivedWidget")("Hello after restart");
-    REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
-}
-
-TEST_CASE("Execution frame") {
-    // When the interpreter is embedded, there is no execution frame, but `py::exec`
-    // should still function by using reasonable globals: `__main__.__dict__`.
-    py::exec("var = dict(number=42)");
-    REQUIRE(py::globals()["var"]["number"].cast<int>() == 42);
-}
-
-TEST_CASE("Threads") {
-    // Restart interpreter to ensure threads are not initialized
-    py::finalize_interpreter();
-    py::initialize_interpreter();
-
-    constexpr auto num_threads = 10;
-    auto locals = py::dict("count"_a = 0);
-
-    {
-        py::gil_scoped_release gil_release{};
-#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX < 0x030E0000
-        std::mutex mutex;
-#endif
-
-        auto threads = std::vector<std::thread>();
-        for (auto i = 0; i < num_threads; ++i) {
-            threads.emplace_back([&]() {
-                py::gil_scoped_acquire gil{};
-#ifdef Py_GIL_DISABLED
-#    if PY_VERSION_HEX < 0x030E0000
-                // This will not run with the GIL, so it won't deadlock. That's
-                // because of how we run our tests. Be more careful of
-                // deadlocks if the "free-threaded" GIL could be enabled (at
-                // runtime).
-                std::lock_guard<std::mutex> lock(mutex);
-#    else
-                // CPython's thread-safe API in no-GIL mode.
-                py::scoped_critical_section lock(locals);
-#    endif
-#endif
-                locals["count"] = locals["count"].cast<int>() + 1;
-            });
-        }
-
-        for (auto &thread : threads) {
-            thread.join();
-        }
-    }
-
-    REQUIRE(locals["count"].cast<int>() == num_threads);
-}
-
-// Scope exit utility https://stackoverflow.com/a/36644501/7255855
-struct scope_exit {
-    std::function<void()> f_;
-    explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
-    ~scope_exit() {
-        if (f_) {
-            f_();
-        }
-    }
-};
-
-TEST_CASE("Reload module from file") {
-    // Disable generation of cached bytecode (.pyc files) for this test, otherwise
-    // Python might pick up an old version from the cache instead of the new versions
-    // of the .py files generated below
-    auto sys = py::module_::import("sys");
-    bool dont_write_bytecode = sys.attr("dont_write_bytecode").cast<bool>();
-    sys.attr("dont_write_bytecode") = true;
-    // Reset the value at scope exit
-    scope_exit reset_dont_write_bytecode(
-        [&]() { sys.attr("dont_write_bytecode") = dont_write_bytecode; });
-
-    std::string module_name = "test_module_reload";
-    std::string module_file = module_name + ".py";
-
-    // Create the module .py file
-    std::ofstream test_module(module_file);
-    test_module << "def test():\n";
-    test_module << "    return 1\n";
-    test_module.close();
-    // Delete the file at scope exit
-    scope_exit delete_module_file([&]() { std::remove(module_file.c_str()); });
-
-    // Import the module from file
-    auto module_ = py::module_::import(module_name.c_str());
-    int result = module_.attr("test")().cast<int>();
-    REQUIRE(result == 1);
-
-    // Update the module .py file with a small change
-    test_module.open(module_file);
-    test_module << "def test():\n";
-    test_module << "    return 2\n";
-    test_module.close();
-
-    // Reload the module
-    module_.reload();
-    result = module_.attr("test")().cast<int>();
-    REQUIRE(result == 2);
-}
-
-TEST_CASE("sys.argv gets initialized properly") {
-    py::finalize_interpreter();
-    {
-        py::scoped_interpreter default_scope;
-        auto module = py::module::import("test_interpreter");
-        auto py_widget = module.attr("DerivedWidget")("The question");
-        const auto &cpp_widget = py_widget.cast<const Widget &>();
-        REQUIRE(cpp_widget.argv0().empty());
-    }
-
-    {
-        char *argv[] = {strdup("a.out")};
-        py::scoped_interpreter argv_scope(true, 1, argv);
-        std::free(argv[0]);
-        auto module = py::module::import("test_interpreter");
-        auto py_widget = module.attr("DerivedWidget")("The question");
-        const auto &cpp_widget = py_widget.cast<const Widget &>();
-        REQUIRE(cpp_widget.argv0() == "a.out");
-    }
-    py::initialize_interpreter();
-}
-
-TEST_CASE("make_iterator can be called before then after finalizing an interpreter") {
-    // Reproduction of issue #2101 (https://github.com/pybind/pybind11/issues/2101)
-    py::finalize_interpreter();
-
-    std::vector<int> container;
-    {
-        pybind11::scoped_interpreter g;
-        auto iter = pybind11::make_iterator(container.begin(), container.end());
-    }
-
-    REQUIRE_NOTHROW([&]() {
-        pybind11::scoped_interpreter g;
-        auto iter = pybind11::make_iterator(container.begin(), container.end());
-    }());
-
-    py::initialize_interpreter();
-}
diff -Nru pybind11-3.0.1/tests/test_embed/test_interpreter.py pybind11-3.0.4/tests/test_embed/test_interpreter.py
--- pybind11-3.0.1/tests/test_embed/test_interpreter.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/test_interpreter.py	1970-01-01 02:00:00.000000000 +0200
@@ -1,16 +0,0 @@
-from __future__ import annotations
-
-import sys
-
-from widget_module import Widget
-
-
-class DerivedWidget(Widget):
-    def __init__(self, message):
-        super().__init__(message)
-
-    def the_answer(self):
-        return 42
-
-    def argv0(self):
-        return sys.argv[0]
diff -Nru pybind11-3.0.1/tests/test_embed/test_subinterpreter.cpp pybind11-3.0.4/tests/test_embed/test_subinterpreter.cpp
--- pybind11-3.0.1/tests/test_embed/test_subinterpreter.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/test_subinterpreter.cpp	1970-01-01 02:00:00.000000000 +0200
@@ -1,442 +0,0 @@
-#include <pybind11/embed.h>
-#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
-#    include <pybind11/subinterpreter.h>
-
-// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
-// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
-PYBIND11_WARNING_DISABLE_MSVC(4996)
-
-#    include <catch.hpp>
-#    include <cstdlib>
-#    include <fstream>
-#    include <functional>
-#    include <thread>
-#    include <utility>
-
-namespace py = pybind11;
-using namespace py::literals;
-
-bool has_state_dict_internals_obj();
-uintptr_t get_details_as_uintptr();
-
-void unsafe_reset_internals_for_single_interpreter() {
-    // NOTE: This code is NOT SAFE unless the caller guarantees no other threads are alive
-    // NOTE: This code is tied to the precise implementation of the internals holder
-
-    // first, unref the thread local internals
-    py::detail::get_internals_pp_manager().unref();
-    py::detail::get_local_internals_pp_manager().unref();
-
-    // we know there are no other interpreters, so we can lower this. SUPER DANGEROUS
-    py::detail::get_num_interpreters_seen() = 1;
-
-    // now we unref the static global singleton internals
-    py::detail::get_internals_pp_manager().unref();
-    py::detail::get_local_internals_pp_manager().unref();
-
-    // finally, we reload the static global singleton
-    py::detail::get_internals();
-    py::detail::get_local_internals();
-}
-
-TEST_CASE("Single Subinterpreter") {
-    unsafe_reset_internals_for_single_interpreter();
-
-    py::module_::import("external_module"); // in the main interpreter
-
-    // Add tags to the modules in the main interpreter and test the basics.
-    py::module_::import("__main__").attr("main_tag") = "main interpreter";
-    {
-        auto m = py::module_::import("widget_module");
-        m.attr("extension_module_tag") = "added to module in main interpreter";
-
-        REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
-    }
-    REQUIRE(has_state_dict_internals_obj());
-
-    auto main_int
-        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-
-    /// Create and switch to a subinterpreter.
-    {
-        py::scoped_subinterpreter ssi;
-
-        // The subinterpreter has internals populated
-        REQUIRE(has_state_dict_internals_obj());
-
-        py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-
-        auto ext_int
-            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-        py::detail::get_internals();
-        REQUIRE(get_details_as_uintptr() == ext_int);
-        REQUIRE(ext_int != main_int);
-
-        // Modules tags should be gone.
-        REQUIRE_FALSE(py::hasattr(py::module_::import("__main__"), "tag"));
-        {
-            auto m = py::module_::import("widget_module");
-            REQUIRE_FALSE(py::hasattr(m, "extension_module_tag"));
-
-            // Function bindings should still work.
-            REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
-        }
-    }
-
-    REQUIRE(py::hasattr(py::module_::import("__main__"), "main_tag"));
-    REQUIRE(py::hasattr(py::module_::import("widget_module"), "extension_module_tag"));
-    REQUIRE(has_state_dict_internals_obj());
-
-    unsafe_reset_internals_for_single_interpreter();
-}
-
-#    if PY_VERSION_HEX >= 0x030D0000
-TEST_CASE("Move Subinterpreter") {
-    std::unique_ptr<py::subinterpreter> sub(new py::subinterpreter(py::subinterpreter::create()));
-
-    // on this thread, use the subinterpreter and import some non-trivial junk
-    {
-        py::subinterpreter_scoped_activate activate(*sub);
-
-        py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-        py::module_::import("datetime");
-        py::module_::import("threading");
-        py::module_::import("external_module");
-    }
-
-    std::thread([&]() {
-        // Use it again
-        {
-            py::subinterpreter_scoped_activate activate(*sub);
-            py::module_::import("external_module");
-        }
-        sub.reset();
-    }).join();
-
-    REQUIRE(!sub);
-
-    unsafe_reset_internals_for_single_interpreter();
-}
-#    endif
-
-TEST_CASE("GIL Subinterpreter") {
-
-    PyInterpreterState *main_interp = PyInterpreterState_Get();
-
-    {
-        auto sub = py::subinterpreter::create();
-
-        REQUIRE(main_interp == PyInterpreterState_Get());
-
-        PyInterpreterState *sub_interp = nullptr;
-
-        {
-            py::subinterpreter_scoped_activate activate(sub);
-
-            sub_interp = PyInterpreterState_Get();
-            REQUIRE(sub_interp != main_interp);
-
-            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-            py::module_::import("datetime");
-            py::module_::import("threading");
-            py::module_::import("external_module");
-
-            {
-                py::subinterpreter_scoped_activate main(py::subinterpreter::main());
-                REQUIRE(PyInterpreterState_Get() == main_interp);
-
-                {
-                    py::gil_scoped_release nogil{};
-                    {
-                        py::gil_scoped_acquire yesgil{};
-                        REQUIRE(PyInterpreterState_Get() == main_interp);
-                    }
-                }
-
-                REQUIRE(PyInterpreterState_Get() == main_interp);
-            }
-
-            REQUIRE(PyInterpreterState_Get() == sub_interp);
-
-            {
-                py::gil_scoped_release nogil{};
-                {
-                    py::gil_scoped_acquire yesgil{};
-                    REQUIRE(PyInterpreterState_Get() == sub_interp);
-                }
-            }
-
-            REQUIRE(PyInterpreterState_Get() == sub_interp);
-        }
-
-        REQUIRE(PyInterpreterState_Get() == main_interp);
-
-        {
-            py::gil_scoped_release nogil{};
-            {
-                py::gil_scoped_acquire yesgil{};
-                REQUIRE(PyInterpreterState_Get() == main_interp);
-            }
-        }
-
-        REQUIRE(PyInterpreterState_Get() == main_interp);
-
-        bool thread_result;
-
-        {
-            thread_result = false;
-            py::gil_scoped_release nogil{};
-            std::thread([&]() {
-                {
-                    py::subinterpreter_scoped_activate ssa{sub};
-                }
-                {
-                    py::gil_scoped_acquire gil{};
-                    thread_result = (PyInterpreterState_Get() == main_interp);
-                }
-            }).join();
-        }
-        REQUIRE(thread_result);
-
-        {
-            thread_result = false;
-            py::gil_scoped_release nogil{};
-            std::thread([&]() {
-                py::gil_scoped_acquire gil{};
-                thread_result = (PyInterpreterState_Get() == main_interp);
-            }).join();
-        }
-        REQUIRE(thread_result);
-    }
-
-    REQUIRE(PyInterpreterState_Get() == main_interp);
-    unsafe_reset_internals_for_single_interpreter();
-}
-
-TEST_CASE("Multiple Subinterpreters") {
-    unsafe_reset_internals_for_single_interpreter();
-
-    // Make sure the module is in the main interpreter and save its pointer
-    auto *main_ext = py::module_::import("external_module").ptr();
-    auto main_int
-        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-    py::module_::import("external_module").attr("multi_interp") = "1";
-
-    {
-        py::subinterpreter si1 = py::subinterpreter::create();
-        std::unique_ptr<py::subinterpreter> psi2;
-
-        PyObject *sub1_ext = nullptr;
-        PyObject *sub2_ext = nullptr;
-        uintptr_t sub1_int = 0;
-        uintptr_t sub2_int = 0;
-
-        {
-            py::subinterpreter_scoped_activate scoped(si1);
-            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-
-            // The subinterpreter has its own copy of this module which is completely separate from
-            // main
-            sub1_ext = py::module_::import("external_module").ptr();
-            REQUIRE(sub1_ext != main_ext);
-            REQUIRE_FALSE(py::hasattr(py::module_::import("external_module"), "multi_interp"));
-            py::module_::import("external_module").attr("multi_interp") = "2";
-            // The subinterpreter also has its own internals
-            sub1_int
-                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-            REQUIRE(sub1_int != main_int);
-
-            // while the old one is active, create a new one
-            psi2.reset(new py::subinterpreter(py::subinterpreter::create()));
-        }
-
-        {
-            py::subinterpreter_scoped_activate scoped(*psi2);
-            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-
-            // The second subinterpreter is separate from both main and the other subinterpreter
-            sub2_ext = py::module_::import("external_module").ptr();
-            REQUIRE(sub2_ext != main_ext);
-            REQUIRE(sub2_ext != sub1_ext);
-            REQUIRE_FALSE(py::hasattr(py::module_::import("external_module"), "multi_interp"));
-            py::module_::import("external_module").attr("multi_interp") = "3";
-            // The subinterpreter also has its own internals
-            sub2_int
-                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-            REQUIRE(sub2_int != main_int);
-            REQUIRE(sub2_int != sub1_int);
-        }
-
-        {
-            py::subinterpreter_scoped_activate scoped(si1);
-            REQUIRE(
-                py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
-                == "2");
-        }
-
-        // out here we should be in the main interpreter, with the GIL, with the other 2 still
-        // alive
-
-        auto post_int
-            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-        // Make sure internals went back the way it was before
-        REQUIRE(main_int == post_int);
-
-        REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
-                == "1");
-    }
-
-    // now back to just main
-
-    auto post_int
-        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-    // Make sure internals went back the way it was before
-    REQUIRE(main_int == post_int);
-
-    REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
-            == "1");
-
-    unsafe_reset_internals_for_single_interpreter();
-}
-
-#    ifdef Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
-TEST_CASE("Per-Subinterpreter GIL") {
-    auto main_int
-        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-
-    std::atomic<int> started, sync, failure;
-    started = 0;
-    sync = 0;
-    failure = 0;
-
-// REQUIRE throws on failure, so we can't use it within the thread
-#        define T_REQUIRE(status)                                                                 \
-            do {                                                                                  \
-                assert(status);                                                                   \
-                if (!(status))                                                                    \
-                    ++failure;                                                                    \
-            } while (0)
-
-    auto &&thread_main = [&](int num) {
-        while (started == 0)
-            std::this_thread::sleep_for(std::chrono::microseconds(1));
-        ++started;
-
-        py::gil_scoped_acquire gil;
-
-        // we have the GIL, we can access the main interpreter
-        auto t_int
-            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-        T_REQUIRE(t_int == main_int);
-        py::module_::import("external_module").attr("multi_interp") = "1";
-
-        auto sub = py::subinterpreter::create();
-
-        {
-            py::subinterpreter_scoped_activate sguard{sub};
-
-            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
-
-            // we have switched to the new interpreter and released the main gil
-
-            // trampoline_module did not provide the per_interpreter_gil tag, so it cannot be
-            // imported
-            bool caught = false;
-            try {
-                py::module_::import("trampoline_module");
-            } catch (pybind11::error_already_set &pe) {
-                T_REQUIRE(pe.matches(PyExc_ImportError));
-                std::string msg(pe.what());
-                T_REQUIRE(msg.find("does not support loading in subinterpreters")
-                          != std::string::npos);
-                caught = true;
-            }
-            T_REQUIRE(caught);
-
-            // widget_module did provide the per_interpreter_gil tag, so it this does not throw
-            try {
-                py::module_::import("widget_module");
-                caught = false;
-            } catch (pybind11::error_already_set &) {
-                caught = true;
-            }
-            T_REQUIRE(!caught);
-
-            // widget_module did provide the per_interpreter_gil tag, so it this does not throw
-            py::module_::import("widget_module");
-
-            T_REQUIRE(!py::hasattr(py::module_::import("external_module"), "multi_interp"));
-            py::module_::import("external_module").attr("multi_interp") = std::to_string(num);
-
-            // wait for something to set sync to our thread number
-            // we are holding our subinterpreter's GIL
-            while (sync != num)
-                std::this_thread::sleep_for(std::chrono::microseconds(1));
-
-            // now change it so the next thread can move on
-            ++sync;
-
-            // but keep holding the GIL until after the next thread moves on as well
-            while (sync == num + 1)
-                std::this_thread::sleep_for(std::chrono::microseconds(1));
-
-            // one last check before quitting the thread, the internals should be different
-            auto sub_int
-                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
-            T_REQUIRE(sub_int != main_int);
-        }
-    };
-#        undef T_REQUIRE
-
-    std::thread t1(thread_main, 1);
-    std::thread t2(thread_main, 2);
-
-    // we spawned two threads, at this point they are both waiting for started to increase
-    ++started;
-
-    // ok now wait for the threads to start
-    while (started != 3)
-        std::this_thread::sleep_for(std::chrono::microseconds(1));
-
-    // we still hold the main GIL, at this point both threads are waiting on the main GIL
-    // IN THE CASE of free threading, the threads are waiting on sync (because there is no GIL)
-
-    // IF the below code hangs in one of the wait loops, then the child thread GIL behavior did not
-    // function as expected.
-    {
-        // release the GIL and allow the threads to run
-        py::gil_scoped_release nogil;
-
-        // the threads are now waiting on the sync
-        REQUIRE(sync == 0);
-
-        // this will trigger thread 1 and then advance and trigger 2 and then advance
-        sync = 1;
-
-        // wait for thread 2 to advance
-        while (sync != 3)
-            std::this_thread::sleep_for(std::chrono::microseconds(1));
-
-        // we know now that thread 1 has run and may be finishing
-        // and thread 2 is waiting for permission to advance
-
-        // so we move sync so that thread 2 can finish executing
-        ++sync;
-
-        // now wait for both threads to complete
-        t1.join();
-        t2.join();
-    }
-
-    // now we have the gil again, sanity check
-    REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
-            == "1");
-
-    unsafe_reset_internals_for_single_interpreter();
-
-    // make sure nothing unexpected happened inside the threads, now that they are completed
-    REQUIRE(failure == 0);
-}
-#    endif // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
-
-#endif // PYBIND11_HAS_SUBINTERPRETER_SUPPORT
diff -Nru pybind11-3.0.1/tests/test_embed/test_trampoline.py pybind11-3.0.4/tests/test_embed/test_trampoline.py
--- pybind11-3.0.1/tests/test_embed/test_trampoline.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_embed/test_trampoline.py	1970-01-01 02:00:00.000000000 +0200
@@ -1,18 +0,0 @@
-from __future__ import annotations
-
-import trampoline_module
-
-
-def func():
-    class Test(trampoline_module.test_override_cache_helper):
-        def func(self):
-            return 42
-
-    return Test()
-
-
-def func2():
-    class Test(trampoline_module.test_override_cache_helper):
-        pass
-
-    return Test()
diff -Nru pybind11-3.0.1/tests/test_enum.py pybind11-3.0.4/tests/test_enum.py
--- pybind11-3.0.1/tests/test_enum.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_enum.py	2026-04-19 06:01:51.000000000 +0300
@@ -328,7 +328,7 @@
         )
         assert (
             re.match(
-                r"^__setstate__\(self: [\w\.]+, state: [\w\.]+, /\)",
+                r"^__setstate__\(self: [\w\.]+, state: [\w\. \|]+, /\)",
                 enum_type.__setstate__.__doc__,
             )
             is not None
diff -Nru pybind11-3.0.1/tests/test_exceptions.py pybind11-3.0.4/tests/test_exceptions.py
--- pybind11-3.0.1/tests/test_exceptions.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_exceptions.py	2026-04-19 06:01:51.000000000 +0300
@@ -76,9 +76,9 @@
 
 # TODO: FIXME
 @pytest.mark.xfail(
-    "(env.MACOS and env.PYPY) or env.ANDROID",
+    "(env.MACOS and env.PYPY) or env.ANDROID or env.FREEBSD",
     raises=RuntimeError,
-    reason="See Issue #2847, PR #2999, PR #4324",
+    reason="See Issue #2847, PR #2999, PR #4324, PR #5925",
     strict=not env.PYPY,  # PR 5569
 )
 def test_cross_module_exception_translator():
diff -Nru pybind11-3.0.1/tests/test_factory_constructors.cpp pybind11-3.0.4/tests/test_factory_constructors.cpp
--- pybind11-3.0.1/tests/test_factory_constructors.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_factory_constructors.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -73,7 +73,7 @@
 // Inheritance test
 class TestFactory4 : public TestFactory3 {
 public:
-    TestFactory4() : TestFactory3() { print_default_created(this); }
+    TestFactory4() { print_default_created(this); }
     explicit TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
     ~TestFactory4() override { print_destroyed(this); }
 };
@@ -376,10 +376,14 @@
             py::print("noisy placement new");
             return p;
         }
-        static void operator delete(void *p, size_t) {
+        static void operator delete(void *p) noexcept {
             py::print("noisy delete");
             ::operator delete(p);
         }
+        static void operator delete(void *p, size_t) {
+            py::print("noisy delete size");
+            ::operator delete(p);
+        }
         static void operator delete(void *, void *) { py::print("noisy placement delete"); }
     };
 
diff -Nru pybind11-3.0.1/tests/test_factory_constructors.py pybind11-3.0.4/tests/test_factory_constructors.py
--- pybind11-3.0.1/tests/test_factory_constructors.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_factory_constructors.py	2026-04-19 06:01:51.000000000 +0300
@@ -78,10 +78,10 @@
         msg(excinfo.value)
         == """
         __init__(): incompatible constructor arguments. The following argument types are supported:
-            1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt)
+            1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt | typing.SupportsIndex)
             2. m.factory_constructors.TestFactory1(arg0: str)
             3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
-            4. m.factory_constructors.TestFactory1(arg0: object, arg1: typing.SupportsInt, arg2: object)
+            4. m.factory_constructors.TestFactory1(arg0: object, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: object)
 
         Invoked with: 'invalid', 'constructor', 'arguments'
     """
@@ -93,13 +93,13 @@
         __init__(*args, **kwargs)
         Overloaded function.
 
-        1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt) -> None
+        1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt | typing.SupportsIndex) -> None
 
         2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
 
         3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
 
-        4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: typing.SupportsInt, arg2: object) -> None
+        4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: object) -> None
     """
     )
 
diff -Nru pybind11-3.0.1/tests/test_gil_scoped.py pybind11-3.0.4/tests/test_gil_scoped.py
--- pybind11-3.0.1/tests/test_gil_scoped.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_gil_scoped.py	2026-04-19 06:01:51.000000000 +0300
@@ -168,6 +168,9 @@
 
 
 def _run_in_process(target, *args, **kwargs):
+    if env.ANDROID or env.IOS or sys.platform.startswith("emscripten"):
+        pytest.skip("Requires subprocess support")
+
     test_fn = target if len(args) == 0 else args[0]
     # Do not need to wait much, 10s should be more than enough.
     timeout = 0.1 if test_fn is _intentional_deadlock else 10
@@ -199,8 +202,14 @@
         if process.exitcode is None:
             assert t_delta > 0.9 * timeout
             msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
-            if env.PYPY and env.WIN:
-                pytest.skip(msg)
+            soabi = sysconfig.get_config_var("SOABI")
+            if env.WIN and env.PYPY:
+                pytest.xfail(f"[TEST-GIL-SCOPED] {soabi} PyPy: " + msg)
+            if env.MACOS:
+                if not env.sys_is_gil_enabled():
+                    pytest.xfail(f"[TEST-GIL-SCOPED] {soabi} with GIL disabled: " + msg)
+                if env.PY_GIL_DISABLED:
+                    pytest.xfail(f"[TEST-GIL-SCOPED] {soabi}: " + msg)
             raise RuntimeError(msg)
         return process.exitcode
     finally:
diff -Nru pybind11-3.0.1/tests/test_iostream.cpp pybind11-3.0.4/tests/test_iostream.cpp
--- pybind11-3.0.1/tests/test_iostream.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_iostream.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -123,4 +123,40 @@
         .def("stop", &TestThread::stop)
         .def("join", &TestThread::join)
         .def("sleep", &TestThread::sleep);
+
+    m.def("move_redirect_output", [](const std::string &msg_before, const std::string &msg_after) {
+        py::scoped_ostream_redirect redir1(std::cout, py::module_::import("sys").attr("stdout"));
+        std::cout << msg_before << std::flush;
+        py::scoped_ostream_redirect redir2(std::move(redir1));
+        std::cout << msg_after << std::flush;
+    });
+
+    m.def("move_redirect_output_unflushed",
+          [](const std::string &msg_before, const std::string &msg_after) {
+              py::scoped_ostream_redirect redir1(std::cout,
+                                                 py::module_::import("sys").attr("stdout"));
+              std::cout << msg_before;
+              py::scoped_ostream_redirect redir2(std::move(redir1));
+              std::cout << msg_after << std::flush;
+          });
+
+    // Redirect a stream whose original rdbuf is nullptr, then move the redirect.
+    // Verifies that nullptr is correctly restored (not confused with a moved-from sentinel).
+    m.def("move_redirect_null_rdbuf", [](const std::string &msg) {
+        std::ostream os(nullptr);
+        py::scoped_ostream_redirect redir1(os, py::module_::import("sys").attr("stdout"));
+        os << msg << std::flush;
+        py::scoped_ostream_redirect redir2(std::move(redir1));
+        os << msg << std::flush;
+        // After redir2 goes out of scope, os.rdbuf() should be restored to nullptr.
+    });
+
+    m.def("get_null_rdbuf_restored", [](const std::string &msg) -> bool {
+        std::ostream os(nullptr);
+        {
+            py::scoped_ostream_redirect redir(os, py::module_::import("sys").attr("stdout"));
+            os << msg << std::flush;
+        }
+        return os.rdbuf() == nullptr;
+    });
 }
diff -Nru pybind11-3.0.1/tests/test_iostream.py pybind11-3.0.4/tests/test_iostream.py
--- pybind11-3.0.1/tests/test_iostream.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_iostream.py	2026-04-19 06:01:51.000000000 +0300
@@ -6,8 +6,19 @@
 
 import pytest
 
+import env
 from pybind11_tests import iostream as m
 
+if env.WIN:
+    wv_build = sys.getwindowsversion().build
+    skip_if_ge = 26100
+    if wv_build >= skip_if_ge:
+        pytest.skip(
+            f"Windows build {wv_build} >= {skip_if_ge}:"
+            " Skipping iostream capture (redirection regression needs investigation)",
+            allow_module_level=True,
+        )
+
 
 def test_captured(capsys):
     msg = "I've been redirected to Python, I hope!"
@@ -273,15 +284,36 @@
     assert stream2.getvalue() == msg2
 
 
+def test_move_redirect(capsys):
+    m.move_redirect_output("before_move", "after_move")
+    stdout, stderr = capsys.readouterr()
+    assert stdout == "before_moveafter_move"
+    assert not stderr
+
+
+def test_move_redirect_unflushed(capsys):
+    m.move_redirect_output_unflushed("before_move", "after_move")
+    stdout, stderr = capsys.readouterr()
+    assert stdout == "before_moveafter_move"
+    assert not stderr
+
+
+def test_move_redirect_null_rdbuf(capsys):
+    m.move_redirect_null_rdbuf("hello")
+    stdout, stderr = capsys.readouterr()
+    assert stdout == "hellohello"
+    assert not stderr
+
+
+def test_null_rdbuf_restored():
+    assert m.get_null_rdbuf_restored("test")
+
+
 @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
 def test_threading():
     with m.ostream_redirect(stdout=True, stderr=False):
         # start some threads
-        threads = []
-
-        # start some threads
-        for _j in range(20):
-            threads.append(m.TestThread())
+        threads = [m.TestThread() for _j in range(20)]
 
         # give the threads some time to fail
         threads[0].sleep()
diff -Nru pybind11-3.0.1/tests/test_kwargs_and_defaults.py pybind11-3.0.4/tests/test_kwargs_and_defaults.py
--- pybind11-3.0.1/tests/test_kwargs_and_defaults.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_kwargs_and_defaults.py	2026-04-19 06:01:51.000000000 +0300
@@ -9,44 +9,45 @@
 def test_function_signatures(doc):
     assert (
         doc(m.kw_func0)
-        == "kw_func0(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> str"
+        == "kw_func0(arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> str"
     )
     assert (
         doc(m.kw_func1)
-        == "kw_func1(x: typing.SupportsInt, y: typing.SupportsInt) -> str"
+        == "kw_func1(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex) -> str"
     )
     assert (
         doc(m.kw_func2)
-        == "kw_func2(x: typing.SupportsInt = 100, y: typing.SupportsInt = 200) -> str"
+        == "kw_func2(x: typing.SupportsInt | typing.SupportsIndex = 100, y: typing.SupportsInt | typing.SupportsIndex = 200) -> str"
     )
     assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None"
     assert (
         doc(m.kw_func4)
-        == "kw_func4(myList: collections.abc.Sequence[typing.SupportsInt] = [13, 17]) -> str"
+        == "kw_func4(myList: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = [13, 17]) -> str"
     )
     assert (
         doc(m.kw_func_udl)
-        == "kw_func_udl(x: typing.SupportsInt, y: typing.SupportsInt = 300) -> str"
+        == "kw_func_udl(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex = 300) -> str"
     )
     assert (
         doc(m.kw_func_udl_z)
-        == "kw_func_udl_z(x: typing.SupportsInt, y: typing.SupportsInt = 0) -> str"
+        == "kw_func_udl_z(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex = 0) -> str"
     )
     assert doc(m.args_function) == "args_function(*args) -> tuple"
     assert (
-        doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple"
+        doc(m.args_kwargs_function)
+        == "args_kwargs_function(*args, **kwargs) -> tuple[tuple, dict[str, typing.Any]]"
     )
     assert (
         doc(m.args_kwargs_subclass_function)
-        == "args_kwargs_subclass_function(*args: str, **kwargs: str) -> tuple"
+        == "args_kwargs_subclass_function(*args: str, **kwargs: str) -> tuple[tuple[str, ...], dict[str, str]]"
     )
     assert (
         doc(m.KWClass.foo0)
-        == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: typing.SupportsInt, arg1: typing.SupportsFloat) -> None"
+        == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex) -> None"
     )
     assert (
         doc(m.KWClass.foo1)
-        == "foo1(self: m.kwargs_and_defaults.KWClass, x: typing.SupportsInt, y: typing.SupportsFloat) -> None"
+        == "foo1(self: m.kwargs_and_defaults.KWClass, x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsFloat | typing.SupportsIndex) -> None"
     )
     assert (
         doc(m.kw_lb_func0)
@@ -138,7 +139,7 @@
         msg(excinfo.value)
         == """
         mixed_plus_args(): incompatible function arguments. The following argument types are supported:
-            1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple
+            1. (arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex, *args) -> tuple[int, float, tuple]
 
         Invoked with: 1
     """
@@ -149,7 +150,7 @@
         msg(excinfo.value)
         == """
         mixed_plus_args(): incompatible function arguments. The following argument types are supported:
-            1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple
+            1. (arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex, *args) -> tuple[int, float, tuple]
 
         Invoked with:
     """
@@ -183,7 +184,7 @@
         msg(excinfo.value)
         == """
         mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
-            1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple
+            1. (i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, **kwargs) -> tuple[int, float, tuple, dict[str, typing.Any]]
 
         Invoked with: 1; kwargs: i=1
     """
@@ -194,7 +195,7 @@
         msg(excinfo.value)
         == """
         mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
-            1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple
+            1. (i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, **kwargs) -> tuple[int, float, tuple, dict[str, typing.Any]]
 
         Invoked with: 1, 2; kwargs: j=1
     """
@@ -211,7 +212,7 @@
         msg(excinfo.value)
         == """
         args_kwonly(): incompatible function arguments. The following argument types are supported:
-            1. (i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt) -> tuple
+            1. (i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsFloat | typing.SupportsIndex, *args, z: typing.SupportsInt | typing.SupportsIndex) -> tuple[int, float, tuple, int]
 
         Invoked with: 2, 2.5, 22
     """
@@ -233,12 +234,12 @@
     )
     assert (
         m.args_kwonly_kwargs.__doc__
-        == "args_kwonly_kwargs(i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt, **kwargs) -> tuple\n"
+        == "args_kwonly_kwargs(i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsFloat | typing.SupportsIndex, *args, z: typing.SupportsInt | typing.SupportsIndex, **kwargs) -> tuple[int, float, tuple, int, dict[str, typing.Any]]\n"
     )
 
     assert (
         m.args_kwonly_kwargs_defaults.__doc__
-        == "args_kwonly_kwargs_defaults(i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n"
+        == "args_kwonly_kwargs_defaults(i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, z: typing.SupportsInt | typing.SupportsIndex = 42, **kwargs) -> tuple[int, float, tuple, int, dict[str, typing.Any]]\n"
     )
     assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
     assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
@@ -294,11 +295,11 @@
     x.method(i=1, j=2)
     assert (
         m.first_arg_kw_only.__init__.__doc__
-        == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 0) -> None\n"
+        == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt | typing.SupportsIndex = 0) -> None\n"
     )
     assert (
         m.first_arg_kw_only.method.__doc__
-        == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 1, j: typing.SupportsInt = 2) -> None\n"
+        == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsInt | typing.SupportsIndex = 2) -> None\n"
     )
 
 
@@ -344,7 +345,7 @@
     # Mix it with args and kwargs:
     assert (
         m.args_kwonly_full_monty.__doc__
-        == "args_kwonly_full_monty(arg0: typing.SupportsInt = 1, arg1: typing.SupportsInt = 2, /, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n"
+        == "args_kwonly_full_monty(arg0: typing.SupportsInt | typing.SupportsIndex = 1, arg1: typing.SupportsInt | typing.SupportsIndex = 2, /, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, z: typing.SupportsInt | typing.SupportsIndex = 42, **kwargs) -> tuple[int, int, float, tuple, int, dict[str, typing.Any]]\n"
     )
     assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
     assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
@@ -387,30 +388,30 @@
     # https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
     assert (
         m.first_arg_kw_only.pos_only.__doc__
-        == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: typing.SupportsInt, j: typing.SupportsInt) -> None\n"
+        == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex) -> None\n"
     )
 
 
 def test_signatures():
     assert (
         m.kw_only_all.__doc__
-        == "kw_only_all(*, i: typing.SupportsInt, j: typing.SupportsInt) -> tuple\n"
+        == "kw_only_all(*, i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex) -> tuple[int, int]\n"
     )
     assert (
         m.kw_only_mixed.__doc__
-        == "kw_only_mixed(i: typing.SupportsInt, *, j: typing.SupportsInt) -> tuple\n"
+        == "kw_only_mixed(i: typing.SupportsInt | typing.SupportsIndex, *, j: typing.SupportsInt | typing.SupportsIndex) -> tuple[int, int]\n"
     )
     assert (
         m.pos_only_all.__doc__
-        == "pos_only_all(i: typing.SupportsInt, j: typing.SupportsInt, /) -> tuple\n"
+        == "pos_only_all(i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex, /) -> tuple[int, int]\n"
     )
     assert (
         m.pos_only_mix.__doc__
-        == "pos_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt) -> tuple\n"
+        == "pos_only_mix(i: typing.SupportsInt | typing.SupportsIndex, /, j: typing.SupportsInt | typing.SupportsIndex) -> tuple[int, int]\n"
     )
     assert (
         m.pos_kw_only_mix.__doc__
-        == "pos_kw_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt, *, k: typing.SupportsInt) -> tuple\n"
+        == "pos_kw_only_mix(i: typing.SupportsInt | typing.SupportsIndex, /, j: typing.SupportsInt | typing.SupportsIndex, *, k: typing.SupportsInt | typing.SupportsIndex) -> tuple[int, int, int]\n"
     )
 
 
@@ -457,7 +458,8 @@
     assert refcount(myval) == expected
 
     exp3 = refcount(myval, myval, myval)
-    assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
+    # if we have to create a new tuple internally, then it will hold an extra reference for each item in it.
+    assert m.args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3)
     assert refcount(myval) == expected
 
     # This function takes the first arg as a `py::object` and the rest as a `py::args`.  Unlike the
diff -Nru pybind11-3.0.1/tests/test_local_bindings.cpp pybind11-3.0.4/tests/test_local_bindings.cpp
--- pybind11-3.0.1/tests/test_local_bindings.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_local_bindings.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -21,6 +21,31 @@
     // test_load_external
     m.def("load_external1", [](ExternalType1 &e) { return e.i; });
     m.def("load_external2", [](ExternalType2 &e) { return e.i; });
+    m.def("load_external3", [](ExternalType3 &e) { return e.i; });
+
+    struct SharedKeepAlive {
+        std::shared_ptr<int> contents;
+        int value() const { return contents ? *contents : -20251012; }
+        long use_count() const { return contents.use_count(); }
+    };
+    py::class_<SharedKeepAlive>(m, "SharedKeepAlive")
+        .def_property_readonly("value", &SharedKeepAlive::value)
+        .def_property_readonly("use_count", &SharedKeepAlive::use_count);
+    m.def("load_external2_shared", [](const std::shared_ptr<ExternalType2> &p) {
+        return SharedKeepAlive{std::shared_ptr<int>(p, &p->i)};
+    });
+    m.def("load_external3_shared", [](const std::shared_ptr<ExternalType3> &p) {
+        return SharedKeepAlive{std::shared_ptr<int>(p, &p->i)};
+    });
+    m.def("load_external1_unique", [](std::unique_ptr<ExternalType1> p) { return p->i; });
+    m.def("load_external3_unique", [](std::unique_ptr<ExternalType3> p) { return p->i; });
+
+    // Aspects of set_foreign_holder that are not covered:
+    // - loading a foreign instance into a custom holder should fail
+    // - we're only covering the case where the local module doesn't know
+    //   about the type; the paths where it does (e.g., if both global and
+    //   foreign-module-local bindings exist for the same type) should work
+    //   the same way (they use the same code so they very likely do)
 
     // test_local_bindings
     // Register a class with py::module_local:
diff -Nru pybind11-3.0.1/tests/test_local_bindings.py pybind11-3.0.4/tests/test_local_bindings.py
--- pybind11-3.0.1/tests/test_local_bindings.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_local_bindings.py	2026-04-19 06:01:51.000000000 +0300
@@ -1,5 +1,8 @@
 from __future__ import annotations
 
+import sys
+from contextlib import suppress
+
 import pytest
 
 from pybind11_tests import local_bindings as m
@@ -11,6 +14,7 @@
 
     assert m.load_external1(cm.ExternalType1(11)) == 11
     assert m.load_external2(cm.ExternalType2(22)) == 22
+    assert m.load_external3(cm.ExternalType3(33)) == 33
 
     with pytest.raises(TypeError) as excinfo:
         assert m.load_external2(cm.ExternalType1(21)) == 21
@@ -20,6 +24,36 @@
         assert m.load_external1(cm.ExternalType2(12)) == 12
     assert "incompatible function arguments" in str(excinfo.value)
 
+    def test_shared(val, ctor, loader):
+        obj = ctor(val)
+        with suppress(AttributeError):  # non-cpython VMs don't have getrefcount
+            rc_before = sys.getrefcount(obj)
+        wrapper = loader(obj)
+        # wrapper holds a shared_ptr that keeps obj alive
+        assert wrapper.use_count == 1
+        assert wrapper.value == val
+        with suppress(AttributeError):
+            rc_after = sys.getrefcount(obj)
+            assert rc_after > rc_before
+
+    test_shared(220, cm.ExternalType2, m.load_external2_shared)
+    test_shared(330, cm.ExternalType3, m.load_external3_shared)
+
+    with pytest.raises(TypeError, match="incompatible function arguments"):
+        test_shared(320, cm.ExternalType2, m.load_external3_shared)
+    with pytest.raises(TypeError, match="incompatible function arguments"):
+        test_shared(230, cm.ExternalType3, m.load_external2_shared)
+
+    with pytest.raises(
+        RuntimeError, match="Foreign instance cannot be converted to std::unique_ptr"
+    ):
+        m.load_external1_unique(cm.ExternalType1(2200))
+
+    with pytest.raises(
+        RuntimeError, match="Foreign instance cannot be converted to std::unique_ptr"
+    ):
+        m.load_external3_unique(cm.ExternalType3(3300))
+
 
 def test_local_bindings():
     """Tests that duplicate `py::module_local` class bindings work across modules"""
diff -Nru pybind11-3.0.1/tests/test_methods_and_attributes.cpp pybind11-3.0.4/tests/test_methods_and_attributes.cpp
--- pybind11-3.0.1/tests/test_methods_and_attributes.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_methods_and_attributes.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -11,6 +11,12 @@
 #include "constructor_stats.h"
 #include "pybind11_tests.h"
 
+#if !defined(PYPY_VERSION)
+// Flag set by the capsule destructor in test_dynamic_attr_dealloc_frees_dict_contents.
+// File scope so the captureless capsule destructor (void(*)(void*)) can access it.
+static bool s_dynamic_attr_capsule_freed = false;
+#endif
+
 #if !defined(PYBIND11_OVERLOAD_CAST)
 template <typename... Args>
 using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;
@@ -161,6 +167,123 @@
     double sum() const { return rw_value + ro_value; }
 };
 
+// Issue #2234: noexcept methods in an unregistered base should be bindable on the derived class.
+// In C++17, noexcept is part of the function type, so &Derived::method resolves to
+// a Base member function pointer with noexcept, requiring explicit template specializations.
+class NoexceptUnregisteredBase {
+public:
+    // Exercises cpp_function(Return (Class::*)(Args...) const noexcept, ...)
+    int value() const noexcept { return m_value; }
+    // Exercises cpp_function(Return (Class::*)(Args...) noexcept, ...)
+    void set_value(int v) noexcept { m_value = v; }
+    // Exercises cpp_function(Return (Class::*)(Args...) & noexcept, ...)
+    void increment() & noexcept { ++m_value; }
+    // Exercises cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
+    int capped_value() const & noexcept { return m_value < 100 ? m_value : 100; }
+
+private:
+    int m_value = 99;
+};
+class NoexceptDerived : public NoexceptUnregisteredBase {
+public:
+    using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
+};
+
+// Exercises cpp_function(Return (Class::*)(Args...) &&, ...) and
+// cpp_function(Return (Class::*)(Args...) const &&, ...) via an unregistered base.
+class RValueRefUnregisteredBase {
+public:
+    // Exercises cpp_function(Return (Class::*)(Args...) &&, ...).
+    // Moves m_payload to verify that std::move(*c).*f is used in the lambda body.
+    std::string take() && { // NOLINT(readability-make-member-function-const)
+        return std::move(m_payload);
+    }
+    // Exercises cpp_function(Return (Class::*)(Args...) const &&, ...)
+    int peek() const && { return m_value; }
+#ifdef __cpp_noexcept_function_type
+    // Exercises cpp_function(Return (Class::*)(Args...) && noexcept, ...)
+    std::string take_noexcept() && noexcept { // NOLINT(readability-make-member-function-const)
+        return std::move(m_payload);
+    }
+    // Exercises cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
+    int peek_noexcept() const && noexcept { return m_value; }
+#endif
+
+private:
+    int m_value = 77;
+    std::string m_payload{"rref_payload"};
+};
+class RValueRefDerived : public RValueRefUnregisteredBase {
+public:
+    using RValueRefUnregisteredBase::RValueRefUnregisteredBase;
+};
+
+// Exercises overload_cast with noexcept member function pointers (issue #2234).
+// In C++17, overload_cast must have noexcept variants to resolve noexcept overloads.
+struct NoexceptOverloaded {
+    py::str method(int) noexcept { return "(int)"; }
+    py::str method(int) const noexcept { return "(int) const"; }
+    py::str method(float) noexcept { return "(float)"; }
+};
+// Exercises overload_cast with noexcept free function pointers.
+int noexcept_free_func(int x) noexcept { return x + 1; }
+int noexcept_free_func(float x) noexcept { return static_cast<int>(x) + 2; }
+
+// Exercises overload_cast with ref-qualified member function pointers.
+struct RefQualifiedOverloaded {
+    py::str method(int) & { return "(int) &"; }
+    py::str method(int) const & { return "(int) const &"; }
+    py::str method(float) && { return "(float) &&"; }
+    py::str method(float) const && { return "(float) const &&"; }
+#ifdef __cpp_noexcept_function_type
+    py::str method(long) & noexcept { return "(long) & noexcept"; }
+    py::str method(long) const & noexcept { return "(long) const & noexcept"; }
+    py::str method(double) && noexcept { return "(double) && noexcept"; }
+    py::str method(double) const && noexcept { return "(double) const && noexcept"; }
+#endif
+};
+
+// Compile-only guard: catch overload_cast resolution regressions/ambiguities early.
+using RefQualifiedOverloadedIntCast = py::detail::overload_cast_impl<int>;
+using RefQualifiedOverloadedFloatCast = py::detail::overload_cast_impl<float>;
+static_assert(
+    std::is_same<decltype(RefQualifiedOverloadedIntCast{}(&RefQualifiedOverloaded::method)),
+                 py::str (RefQualifiedOverloaded::*)(int) &>::value,
+    "");
+static_assert(std::is_same<decltype(RefQualifiedOverloadedIntCast{}(
+                               &RefQualifiedOverloaded::method, py::const_)),
+                           py::str (RefQualifiedOverloaded::*)(int) const &>::value,
+              "");
+static_assert(
+    std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(&RefQualifiedOverloaded::method)),
+                 py::str (RefQualifiedOverloaded::*)(float) &&>::value,
+    "");
+static_assert(std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(
+                               &RefQualifiedOverloaded::method, py::const_)),
+                           py::str (RefQualifiedOverloaded::*)(float) const &&>::value,
+              "");
+
+#ifdef __cpp_noexcept_function_type
+using RefQualifiedOverloadedLongCast = py::detail::overload_cast_impl<long>;
+using RefQualifiedOverloadedDoubleCast = py::detail::overload_cast_impl<double>;
+static_assert(
+    std::is_same<decltype(RefQualifiedOverloadedLongCast{}(&RefQualifiedOverloaded::method)),
+                 py::str (RefQualifiedOverloaded::*)(long) & noexcept>::value,
+    "");
+static_assert(std::is_same<decltype(RefQualifiedOverloadedLongCast{}(
+                               &RefQualifiedOverloaded::method, py::const_)),
+                           py::str (RefQualifiedOverloaded::*)(long) const & noexcept>::value,
+              "");
+static_assert(
+    std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(&RefQualifiedOverloaded::method)),
+                 py::str (RefQualifiedOverloaded::*)(double) && noexcept>::value,
+    "");
+static_assert(std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(
+                               &RefQualifiedOverloaded::method, py::const_)),
+                           py::str (RefQualifiedOverloaded::*)(double) const && noexcept>::value,
+              "");
+#endif
+
 // Test explicit lvalue ref-qualification
 struct RefQualified {
     int value = 0;
@@ -388,6 +511,24 @@
 
     class CppDerivedDynamicClass : public DynamicClass {};
     py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass").def(py::init());
+
+    // test_dynamic_attr_dealloc_frees_dict_contents
+    // Regression test: pybind11_object_dealloc() must call PyObject_ClearManagedDict()
+    // before tp_free() so that objects stored in a py::dynamic_attr() instance __dict__
+    // have their refcounts decremented when the pybind11 object is freed. On Python 3.14+
+    // tp_free no longer implicitly clears the managed dict, causing permanent leaks.
+    m.def("make_dynamic_attr_with_capsule", []() -> py::object {
+        s_dynamic_attr_capsule_freed = false;
+        auto *dummy = new int(0);
+        py::capsule cap(dummy, [](void *ptr) {
+            delete static_cast<int *>(ptr);
+            s_dynamic_attr_capsule_freed = true;
+        });
+        py::object obj = py::cast(new DynamicClass(), py::return_value_policy::take_ownership);
+        obj.attr("data") = cap;
+        return obj;
+    });
+    m.def("is_dynamic_attr_capsule_freed", []() { return s_dynamic_attr_capsule_freed; });
 #endif
 
     // test_bad_arg_default
@@ -474,6 +615,161 @@
         = decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
     static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
 
+    // test_noexcept_base (issue #2234)
+    // In C++17, noexcept is part of the function type. Binding a noexcept method from an
+    // unregistered base class must resolve `self` to the derived type, not the base type.
+    py::class_<NoexceptDerived>(m, "NoexceptDerived")
+        .def(py::init<>())
+        // cpp_function(Return (Class::*)(Args...) const noexcept, ...)
+        .def("value", &NoexceptDerived::value)
+        // cpp_function(Return (Class::*)(Args...) noexcept, ...)
+        .def("set_value", &NoexceptDerived::set_value)
+        // cpp_function(Return (Class::*)(Args...) & noexcept, ...)
+        .def("increment", &NoexceptDerived::increment)
+        // cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
+        .def("capped_value", &NoexceptDerived::capped_value);
+
+    // test_rvalue_ref_qualified_methods: rvalue-ref-qualified methods from an unregistered base.
+    // method_adaptor must rebind &&/const&& member pointers to the derived type.
+    py::class_<RValueRefDerived>(m, "RValueRefDerived")
+        .def(py::init<>())
+        // cpp_function(Return (Class::*)(Args...) &&, ...)
+        .def("take", &RValueRefDerived::take)
+        // cpp_function(Return (Class::*)(Args...) const &&, ...)
+        .def("peek", &RValueRefDerived::peek)
+#ifdef __cpp_noexcept_function_type
+        // cpp_function(Return (Class::*)(Args...) && noexcept, ...)
+        .def("take_noexcept", &RValueRefDerived::take_noexcept)
+        // cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
+        .def("peek_noexcept", &RValueRefDerived::peek_noexcept)
+#endif
+        ;
+
+    // Verify that &&-qualified methods cannot be called on lvalues, only on rvalues.
+    // This confirms that the cpp_function lambda must use std::move(*c).*f, not c->*f.
+#if __cplusplus >= 201703L
+    static_assert(!std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
+                                     RValueRefUnregisteredBase &>::value,
+                  "&&-qualified method must not be callable on lvalue");
+    static_assert(std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
+                                    RValueRefUnregisteredBase &&>::value,
+                  "&&-qualified method must be callable on rvalue");
+#endif
+
+    // Verify method_adaptor preserves &&/const&& qualifiers when rebinding.
+    using AdaptedRRef = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take));
+    static_assert(std::is_same<AdaptedRRef, std::string (RValueRefDerived::*)() &&>::value, "");
+    using AdaptedConstRRef
+        = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek));
+    static_assert(std::is_same<AdaptedConstRRef, int (RValueRefDerived::*)() const &&>::value, "");
+
+#ifdef __cpp_noexcept_function_type
+    // method_adaptor must also handle noexcept member function pointers (issue #2234).
+    // Verify the noexcept specifier is preserved in the resulting Derived pointer type.
+    using AdaptedConstNoexcept
+        = decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::value));
+    static_assert(
+        std::is_same<AdaptedConstNoexcept, int (NoexceptDerived::*)() const noexcept>::value, "");
+    using AdaptedNoexcept
+        = decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::set_value));
+    static_assert(std::is_same<AdaptedNoexcept, void (NoexceptDerived::*)(int) noexcept>::value,
+                  "");
+    using AdaptedRRefNoexcept
+        = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take_noexcept));
+    static_assert(std::is_same < AdaptedRRefNoexcept,
+                  std::string (RValueRefDerived::*)() && noexcept > ::value,
+                  "");
+    using AdaptedConstRRefNoexcept
+        = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek_noexcept));
+    static_assert(std::is_same < AdaptedConstRRefNoexcept,
+                  int (RValueRefDerived::*)() const && noexcept > ::value,
+                  "");
+#endif
+
+    // test_noexcept_overload_cast (issue #2234)
+    // overload_cast must have noexcept operator() overloads so it can resolve noexcept methods.
+#ifdef PYBIND11_OVERLOAD_CAST
+    py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
+        .def(py::init<>())
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
+        .def("method", py::overload_cast<int>(&NoexceptOverloaded::method))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
+        .def("method_const", py::overload_cast<int>(&NoexceptOverloaded::method, py::const_))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
+        .def("method_float", py::overload_cast<float>(&NoexceptOverloaded::method));
+    // overload_cast_impl::operator()(Return (*)(Args...) noexcept)
+    m.def("noexcept_free_func", py::overload_cast<int>(noexcept_free_func));
+    m.def("noexcept_free_func_float", py::overload_cast<float>(noexcept_free_func));
+
+    py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
+        .def(py::init<>())
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
+        .def("method_lref", py::overload_cast<int>(&RefQualifiedOverloaded::method))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
+        .def("method_const_lref",
+             py::overload_cast<int>(&RefQualifiedOverloaded::method, py::const_))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
+        .def("method_rref", py::overload_cast<float>(&RefQualifiedOverloaded::method))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
+        .def("method_const_rref",
+             py::overload_cast<float>(&RefQualifiedOverloaded::method, py::const_))
+#    ifdef __cpp_noexcept_function_type
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
+        .def("method_lref_noexcept", py::overload_cast<long>(&RefQualifiedOverloaded::method))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
+        .def("method_const_lref_noexcept",
+             py::overload_cast<long>(&RefQualifiedOverloaded::method, py::const_))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
+        .def("method_rref_noexcept", py::overload_cast<double>(&RefQualifiedOverloaded::method))
+        // overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
+        .def("method_const_rref_noexcept",
+             py::overload_cast<double>(&RefQualifiedOverloaded::method, py::const_))
+#    endif
+        ;
+#else
+    // Fallback using explicit static_cast for C++11/14
+    py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
+        .def(py::init<>())
+        .def("method",
+             static_cast<py::str (NoexceptOverloaded::*)(int)>(&NoexceptOverloaded::method))
+        .def("method_const",
+             static_cast<py::str (NoexceptOverloaded::*)(int) const>(&NoexceptOverloaded::method))
+        .def("method_float",
+             static_cast<py::str (NoexceptOverloaded::*)(float)>(&NoexceptOverloaded::method));
+    m.def("noexcept_free_func", static_cast<int (*)(int)>(noexcept_free_func));
+    m.def("noexcept_free_func_float", static_cast<int (*)(float)>(noexcept_free_func));
+
+    py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
+        .def(py::init<>())
+        .def("method_lref",
+             static_cast<py::str (RefQualifiedOverloaded::*)(int) &>(
+                 &RefQualifiedOverloaded::method))
+        .def("method_const_lref",
+             static_cast<py::str (RefQualifiedOverloaded::*)(int) const &>(
+                 &RefQualifiedOverloaded::method))
+        .def("method_rref",
+             static_cast<py::str (RefQualifiedOverloaded::*)(float) &&>(
+                 &RefQualifiedOverloaded::method))
+        .def("method_const_rref",
+             static_cast<py::str (RefQualifiedOverloaded::*)(float) const &&>(
+                 &RefQualifiedOverloaded::method))
+#    ifdef __cpp_noexcept_function_type
+        .def("method_lref_noexcept",
+             static_cast<py::str (RefQualifiedOverloaded::*)(long) & noexcept>(
+                 &RefQualifiedOverloaded::method))
+        .def("method_const_lref_noexcept",
+             static_cast<py::str (RefQualifiedOverloaded::*)(long) const & noexcept>(
+                 &RefQualifiedOverloaded::method))
+        .def("method_rref_noexcept",
+             static_cast < py::str (RefQualifiedOverloaded::*)(double)
+                 && noexcept > (&RefQualifiedOverloaded::method))
+        .def("method_const_rref_noexcept",
+             static_cast < py::str (RefQualifiedOverloaded::*)(double) const && noexcept
+                 > (&RefQualifiedOverloaded::method))
+#    endif
+        ;
+#endif
+
     // test_methods_and_attributes
     py::class_<RefQualified>(m, "RefQualified")
         .def(py::init<>())
diff -Nru pybind11-3.0.1/tests/test_methods_and_attributes.py pybind11-3.0.4/tests/test_methods_and_attributes.py
--- pybind11-3.0.1/tests/test_methods_and_attributes.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_methods_and_attributes.py	2026-04-19 06:01:51.000000000 +0300
@@ -5,7 +5,7 @@
 import pytest
 
 import env
-from pybind11_tests import ConstructorStats
+from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
 from pybind11_tests import methods_and_attributes as m
 
 NO_GETTER_MSG = (
@@ -251,7 +251,7 @@
             "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
             if not detailed_error_messages_enabled
             else "error while attempting to bind static method ExampleMandA.overload_mixed1"
-            "(arg0: typing.SupportsFloat) -> str"
+            "(arg0: typing.SupportsFloat | typing.SupportsIndex) -> str"
         )
     )
 
@@ -264,7 +264,7 @@
             "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
             if not detailed_error_messages_enabled
             else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
-            "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: typing.SupportsInt, arg1: typing.SupportsInt)"
+            "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex)"
             " -> str"
         )
     )
@@ -383,6 +383,23 @@
     assert cstats.alive() == 0
 
 
+@pytest.mark.xfail("env.PYPY", strict=False)
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_dynamic_attr_dealloc_frees_dict_contents():
+    """Regression: py::dynamic_attr() objects must free __dict__ contents on dealloc.
+
+    pybind11_object_dealloc() did not call PyObject_ClearManagedDict() before tp_free(),
+    causing objects stored in __dict__ to have their refcounts permanently abandoned on
+    Python 3.14+ (where tp_free no longer implicitly clears the managed dict).
+    This caused capsule destructors to never run, leaking the underlying C++ data.
+    """
+    instance = m.make_dynamic_attr_with_capsule()
+    assert not m.is_dynamic_attr_capsule_freed()
+    del instance
+    pytest.gc_collect()
+    assert m.is_dynamic_attr_capsule_freed()
+
+
 def test_bad_arg_default(msg):
     from pybind11_tests import detailed_error_messages_enabled
 
@@ -491,7 +508,7 @@
         msg(excinfo.value)
         == """
         __init__(): incompatible constructor arguments. The following argument types are supported:
-            1. m.methods_and_attributes.StrIssue(arg0: typing.SupportsInt)
+            1. m.methods_and_attributes.StrIssue(arg0: typing.SupportsInt | typing.SupportsIndex)
             2. m.methods_and_attributes.StrIssue()
 
         Invoked with: 'no', 'such', 'constructor'
@@ -517,6 +534,130 @@
     assert a.ro_value_prop == 1.75
 
 
+def test_noexcept_base():
+    """Test issue #2234: binding noexcept methods inherited from an unregistered base class.
+
+    In C++17 noexcept is part of the function type, so &Derived::noexcept_method resolves
+    to a Base member-function pointer with noexcept specifier.  pybind11 must use the Derived
+    type as `self`, not the Base type, otherwise the call raises TypeError at runtime.
+
+    Covers all four new cpp_function constructor specialisations:
+      - Return (Class::*)(Args...) noexcept          (set_value)
+      - Return (Class::*)(Args...) const noexcept    (value)
+      - Return (Class::*)(Args...) & noexcept        (increment)
+      - Return (Class::*)(Args...) const & noexcept  (capped_value)
+    """
+    obj = m.NoexceptDerived()
+    # const noexcept
+    assert obj.value() == 99
+    # noexcept (non-const)
+    obj.set_value(7)
+    assert obj.value() == 7
+    # & noexcept (non-const lvalue ref-qualified)
+    obj.increment()
+    assert obj.value() == 8
+    # const & noexcept (const lvalue ref-qualified)
+    assert obj.capped_value() == 8
+    obj.set_value(200)
+    assert obj.capped_value() == 100  # capped at 100
+
+
+def test_rvalue_ref_qualified_methods():
+    """Test that rvalue-ref-qualified (&&/const&&) methods from an unregistered base bind
+    correctly with `self` resolved to the derived type.
+
+    take() moves m_payload out on each call, so the second call returns "".
+    This confirms that the cpp_function lambda uses std::move(*c).*f rather than c->*f.
+
+    Covers:
+      - Return (Class::*)(Args...) &&              (take)
+      - Return (Class::*)(Args...) const &&        (peek)
+    """
+    obj = m.RValueRefDerived()
+    # && moves m_payload: first call gets the value, second gets empty string
+    assert obj.take() == "rref_payload"
+    assert obj.take() == ""
+    # const && doesn't move: peek() is stable across calls
+    assert obj.peek() == 77
+    assert obj.peek() == 77
+
+
+@pytest.mark.skipif(
+    not defined___cpp_noexcept_function_type,
+    reason="Requires __cpp_noexcept_function_type",
+)
+def test_noexcept_rvalue_ref_qualified_methods():
+    """Test noexcept rvalue-ref-qualified methods from an unregistered base.
+
+    Covers:
+      - Return (Class::*)(Args...) && noexcept       (take_noexcept)
+      - Return (Class::*)(Args...) const && noexcept (peek_noexcept)
+    """
+    obj = m.RValueRefDerived()
+    assert obj.take_noexcept() == "rref_payload"
+    assert obj.take_noexcept() == ""
+    assert obj.peek_noexcept() == 77
+    assert obj.peek_noexcept() == 77
+
+
+def test_noexcept_overload_cast():
+    """Test issue #2234: overload_cast must handle noexcept member and free function pointers.
+
+    In C++17 noexcept is part of the function type, so overload_cast_impl needs dedicated
+    operator() overloads for noexcept free functions and non-const/const member functions.
+    """
+    obj = m.NoexceptOverloaded()
+    # overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
+    assert obj.method(1) == "(int)"
+    # overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
+    assert obj.method_const(2) == "(int) const"
+    # overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
+    assert obj.method_float(3.0) == "(float)"
+    # overload_cast_impl::operator()(Return (*)(Args...) noexcept)
+    assert m.noexcept_free_func(10) == 11
+    assert m.noexcept_free_func_float(10.0) == 12
+
+
+def test_ref_qualified_overload_cast():
+    """Test issue #2234 follow-up: overload_cast with ref-qualified member pointers.
+
+    Covers:
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
+    """
+    obj = m.RefQualifiedOverloaded()
+    assert obj.method_lref(1) == "(int) &"
+    assert obj.method_const_lref(1) == "(int) const &"
+    assert obj.method_rref(1.0) == "(float) &&"
+    assert obj.method_const_rref(1.0) == "(float) const &&"
+
+
+@pytest.mark.skipif(
+    not defined___cpp_noexcept_function_type,
+    reason="Requires __cpp_noexcept_function_type",
+)
+def test_noexcept_ref_qualified_overload_cast():
+    """Test issue #2234 follow-up: overload_cast with noexcept ref-qualified member pointers.
+
+    Covers:
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
+      - overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
+    """
+    obj = m.RefQualifiedOverloaded()
+    assert obj.method_lref_noexcept(1) == "(long) & noexcept"
+    assert obj.method_const_lref_noexcept(1) == "(long) const & noexcept"
+    assert obj.method_rref_noexcept(1.0) == "(double) && noexcept"
+    assert obj.method_const_rref_noexcept(1.0) == "(double) const && noexcept"
+
+
 def test_ref_qualified():
     """Tests that explicit lvalue ref-qualified methods can be called just like their
     non ref-qualified counterparts."""
@@ -534,21 +675,27 @@
     assert m.overload_order(0) == 4
 
     assert (
-        "1. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__
+        "1. overload_order(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
+        in m.overload_order.__doc__
     )
     assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__
     assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__
     assert (
-        "4. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__
+        "4. overload_order(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
+        in m.overload_order.__doc__
     )
 
     with pytest.raises(TypeError) as err:
         m.overload_order(1.1)
 
-    assert "1. (arg0: typing.SupportsInt) -> int" in str(err.value)
+    assert "1. (arg0: typing.SupportsInt | typing.SupportsIndex) -> int" in str(
+        err.value
+    )
     assert "2. (arg0: str) -> int" in str(err.value)
     assert "3. (arg0: str) -> int" in str(err.value)
-    assert "4. (arg0: typing.SupportsInt) -> int" in str(err.value)
+    assert "4. (arg0: typing.SupportsInt | typing.SupportsIndex) -> int" in str(
+        err.value
+    )
 
 
 def test_rvalue_ref_param():
diff -Nru pybind11-3.0.1/tests/test_multiple_interpreters.py pybind11-3.0.4/tests/test_multiple_interpreters.py
--- pybind11-3.0.1/tests/test_multiple_interpreters.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_multiple_interpreters.py	2026-04-19 06:01:51.000000000 +0300
@@ -4,9 +4,16 @@
 import os
 import pickle
 import sys
+import textwrap
 
 import pytest
 
+import env
+import pybind11_tests
+
+if env.IOS:
+    pytest.skip("Subinterpreters not supported on iOS", allow_module_level=True)
+
 # 3.14.0b3+, though sys.implementation.supports_isolated_interpreters is being added in b4
 # Can be simplified when we drop support for the first three betas
 CONCURRENT_INTERPRETERS_SUPPORT = (
@@ -82,21 +89,23 @@
 def test_independent_subinterpreters():
     """Makes sure the internals object differs across independent subinterpreters"""
 
-    sys.path.append(".")
+    sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
 
     run_string, create = get_interpreters(modern=True)
 
-    m = pytest.importorskip("mod_per_interpreter_gil")
+    import mod_per_interpreter_gil as m
 
     if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
         pytest.skip("Does not have subinterpreter support compiled in")
 
-    code = """
-import mod_per_interpreter_gil as m
-import pickle
-with open(pipeo, 'wb') as f:
-    pickle.dump(m.internals_at(), f)
-"""
+    code = textwrap.dedent(
+        """
+        import mod_per_interpreter_gil as m
+        import pickle
+        with open(pipeo, 'wb') as f:
+            pickle.dump(m.internals_at(), f)
+        """
+    ).strip()
 
     with create() as interp1, create() as interp2:
         try:
@@ -131,20 +140,22 @@
 def test_independent_subinterpreters_modern():
     """Makes sure the internals object differs across independent subinterpreters. Modern (3.14+) syntax."""
 
-    sys.path.append(".")
+    sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
 
-    m = pytest.importorskip("mod_per_interpreter_gil")
+    import mod_per_interpreter_gil as m
 
     if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
         pytest.skip("Does not have subinterpreter support compiled in")
 
     from concurrent import interpreters
 
-    code = """
-import mod_per_interpreter_gil as m
-
-values.put_nowait(m.internals_at())
-"""
+    code = textwrap.dedent(
+        """
+        import mod_per_interpreter_gil as m
+
+        values.put_nowait(m.internals_at())
+        """
+    ).strip()
 
     with contextlib.closing(interpreters.create()) as interp1, contextlib.closing(
         interpreters.create()
@@ -175,21 +186,23 @@
 def test_dependent_subinterpreters():
     """Makes sure the internals object differs across subinterpreters"""
 
-    sys.path.append(".")
+    sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
 
     run_string, create = get_interpreters(modern=False)
 
-    m = pytest.importorskip("mod_shared_interpreter_gil")
+    import mod_shared_interpreter_gil as m
 
     if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
         pytest.skip("Does not have subinterpreter support compiled in")
 
-    code = """
-import mod_shared_interpreter_gil as m
-import pickle
-with open(pipeo, 'wb') as f:
-    pickle.dump(m.internals_at(), f)
-"""
+    code = textwrap.dedent(
+        """
+        import mod_shared_interpreter_gil as m
+        import pickle
+        with open(pipeo, 'wb') as f:
+            pickle.dump(m.internals_at(), f)
+        """
+    ).strip()
 
     with create("legacy") as interp1:
         pipei, pipeo = os.pipe()
@@ -198,3 +211,227 @@
             res1 = pickle.load(f)
 
     assert res1 != m.internals_at(), "internals should differ from main interpreter"
+
+
+PREAMBLE_CODE = textwrap.dedent(
+    f"""
+    def test():
+        import sys
+
+        sys.path.insert(0, {os.path.dirname(env.__file__)!r})
+        sys.path.insert(0, {os.path.dirname(pybind11_tests.__file__)!r})
+
+        import collections
+        import mod_per_interpreter_gil_with_singleton as m
+
+        objects = m.get_objects_in_singleton()
+        expected = [
+            type(None),               # static type: shared between interpreters
+            tuple,                    # static type: shared between interpreters
+            list,                     # static type: shared between interpreters
+            dict,                     # static type: shared between interpreters
+            collections.OrderedDict,  # static type: shared between interpreters
+            collections.defaultdict,  # heap type: dynamically created per interpreter
+            collections.deque,        # heap type: dynamically created per interpreter
+        ]
+        # Check that we have the expected objects. Avoid IndexError by checking lengths first.
+        assert len(objects) == len(expected), (
+            f"Expected {{expected!r}} ({{len(expected)}}), got {{objects!r}} ({{len(objects)}})."
+        )
+        # The first ones are static types shared between interpreters.
+        assert objects[:-2] == expected[:-2], (
+            f"Expected static objects {{expected[:-2]!r}}, got {{objects[:-2]!r}}."
+        )
+        # The last two are heap types created per-interpreter.
+        # The expected objects are dynamically imported from `collections`.
+        assert objects[-2:] == expected[-2:], (
+            f"Expected heap objects {{expected[-2:]!r}}, got {{objects[-2:]!r}}."
+        )
+
+        assert hasattr(m, 'MyClass'), "Module missing MyClass"
+        assert hasattr(m, 'MyGlobalError'), "Module missing MyGlobalError"
+        assert hasattr(m, 'MyLocalError'), "Module missing MyLocalError"
+        assert hasattr(m, 'MyEnum'), "Module missing MyEnum"
+    """
+).lstrip()
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith("emscripten"), reason="Requires loadable modules"
+)
+@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
+def test_import_module_with_singleton_per_interpreter():
+    """Tests that a singleton storing Python objects works correctly per-interpreter"""
+    from concurrent import interpreters
+
+    code = f"{PREAMBLE_CODE.strip()}\n\ntest()\n"
+    with contextlib.closing(interpreters.create()) as interp:
+        interp.exec(code)
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith("emscripten"), reason="Requires loadable modules"
+)
+@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
+def test_import_in_subinterpreter_after_main():
+    """Tests that importing a module in a subinterpreter after the main interpreter works correctly"""
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import contextlib
+            import gc
+            from concurrent import interpreters
+
+            test()
+
+            interp = None
+            with contextlib.closing(interpreters.create()) as interp:
+                interp.call(test)
+
+            del interp
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
+
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import contextlib
+            import gc
+            import random
+            from concurrent import interpreters
+
+            test()
+
+            interps = interp = None
+            with contextlib.ExitStack() as stack:
+                interps = [
+                    stack.enter_context(contextlib.closing(interpreters.create()))
+                    for _ in range(8)
+                ]
+                random.shuffle(interps)
+                for interp in interps:
+                    interp.call(test)
+
+            del interps, interp, stack
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith("emscripten"), reason="Requires loadable modules"
+)
+@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
+def test_import_in_subinterpreter_before_main():
+    """Tests that importing a module in a subinterpreter before the main interpreter works correctly"""
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import contextlib
+            import gc
+            from concurrent import interpreters
+
+            interp = None
+            with contextlib.closing(interpreters.create()) as interp:
+                interp.call(test)
+
+            test()
+
+            del interp
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
+
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import contextlib
+            import gc
+            from concurrent import interpreters
+
+            interps = interp = None
+            with contextlib.ExitStack() as stack:
+                interps = [
+                    stack.enter_context(contextlib.closing(interpreters.create()))
+                    for _ in range(8)
+                ]
+                for interp in interps:
+                    interp.call(test)
+
+            test()
+
+            del interps, interp, stack
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
+
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import contextlib
+            import gc
+            from concurrent import interpreters
+
+            interps = interp = None
+            with contextlib.ExitStack() as stack:
+                interps = [
+                    stack.enter_context(contextlib.closing(interpreters.create()))
+                    for _ in range(8)
+                ]
+                for interp in interps:
+                    interp.call(test)
+
+                test()
+
+            del interps, interp, stack
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith("emscripten"), reason="Requires loadable modules"
+)
+@pytest.mark.xfail(
+    env.MUSLLINUX,
+    reason="Flaky on musllinux, see also: https://github.com/pybind/pybind11/pull/5972#discussion_r2755283335",
+    strict=False,
+)
+@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
+def test_import_in_subinterpreter_concurrently():
+    """Tests that importing a module in multiple subinterpreters concurrently works correctly"""
+    env.check_script_success_in_subprocess(
+        PREAMBLE_CODE
+        + textwrap.dedent(
+            """
+            import gc
+            from concurrent.futures import InterpreterPoolExecutor, as_completed
+
+            futures = future = None
+            with InterpreterPoolExecutor(max_workers=16) as executor:
+                futures = [executor.submit(test) for _ in range(32)]
+                for future in as_completed(futures):
+                    future.result()
+            del futures, future, executor
+
+            for _ in range(5):
+                gc.collect()
+            """
+        )
+    )
diff -Nru pybind11-3.0.1/tests/test_native_enum.cpp pybind11-3.0.4/tests/test_native_enum.cpp
--- pybind11-3.0.1/tests/test_native_enum.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_native_enum.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -93,10 +93,14 @@
         .value("blue", color::blue)
         .finalize();
 
-    py::native_enum<altitude>(m, "altitude", "enum.Enum")
-        .value("high", altitude::high)
-        .value("low", altitude::low)
-        .finalize();
+    m.def("bind_altitude", [](const py::module_ &mod) {
+        py::native_enum<altitude>(mod, "altitude", "enum.Enum")
+            .value("high", altitude::high)
+            .value("low", altitude::low)
+            .finalize();
+    });
+    m.def("is_high_altitude", [](altitude alt) { return alt == altitude::high; });
+    m.def("get_altitude", []() -> altitude { return altitude::high; });
 
     py::native_enum<flags_uchar>(m, "flags_uchar", "enum.Flag")
         .value("bit0", flags_uchar::bit0)
diff -Nru pybind11-3.0.1/tests/test_native_enum.py pybind11-3.0.4/tests/test_native_enum.py
--- pybind11-3.0.1/tests/test_native_enum.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_native_enum.py	2026-04-19 06:01:51.000000000 +0300
@@ -59,7 +59,6 @@
 ENUM_TYPES_AND_MEMBERS = (
     (m.smallenum, SMALLENUM_MEMBERS),
     (m.color, COLOR_MEMBERS),
-    (m.altitude, ALTITUDE_MEMBERS),
     (m.flags_uchar, FLAGS_UCHAR_MEMBERS),
     (m.flags_uint, FLAGS_UINT_MEMBERS),
     (m.export_values, EXPORT_VALUES_MEMBERS),
@@ -320,3 +319,59 @@
     if not isinstance(m.native_enum_missing_finalize_failure, str):
         m.native_enum_missing_finalize_failure()
         pytest.fail("Process termination expected.")
+
+
+def test_unregister_native_enum_when_destroyed():
+    # For stability when running tests in parallel, this test should be the
+    # only one that touches `m.altitude` or calls `m.bind_altitude`.
+
+    def test_altitude_enum():
+        # Logic copied from test_enum_type / test_enum_members.
+        # We don't test altitude there to avoid possible clashes if
+        # parallelizing against other tests in this file, and we also
+        # don't want to hold any references to the enumerators that
+        # would prevent GCing the enum type below.
+        assert isinstance(m.altitude, enum.EnumMeta)
+        assert m.altitude.__module__ == m.__name__
+        for name, value in ALTITUDE_MEMBERS:
+            assert m.altitude[name].value == value
+
+    def test_altitude_binding():
+        assert m.is_high_altitude(m.altitude.high)
+        assert not m.is_high_altitude(m.altitude.low)
+        assert m.get_altitude() is m.altitude.high
+        with pytest.raises(TypeError, match="incompatible function arguments"):
+            m.is_high_altitude("oops")
+
+    m.bind_altitude(m)
+    test_altitude_enum()
+    test_altitude_binding()
+
+    if env.TYPES_ARE_IMMORTAL:
+        pytest.skip("can't GC type objects on this platform")
+
+    # Delete the enum type. Returning an instance from Python should fail
+    # rather than accessing a deleted object.
+    pytest.delattr_and_ensure_destroyed((m, "altitude"))
+    with pytest.raises(TypeError, match="Unable to convert function return"):
+        m.get_altitude()
+    with pytest.raises(TypeError, match="incompatible function arguments"):
+        m.is_high_altitude("oops")
+
+    # Recreate the enum type; should not have any duplicate-binding error
+    m.bind_altitude(m)
+    test_altitude_enum()
+    test_altitude_binding()
+
+    # Remove the pybind11 capsule without removing the type; enum is still
+    # usable but can't be passed to/from bound functions
+    del m.altitude.__pybind11_native_enum__
+    pytest.gc_collect()
+    test_altitude_enum()  # enum itself still works
+
+    with pytest.raises(TypeError, match="Unable to convert function return"):
+        m.get_altitude()
+    with pytest.raises(TypeError, match="incompatible function arguments"):
+        m.is_high_altitude(m.altitude.high)
+
+    del m.altitude
diff -Nru pybind11-3.0.1/tests/test_numpy_array.cpp pybind11-3.0.4/tests/test_numpy_array.cpp
--- pybind11-3.0.1/tests/test_numpy_array.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_numpy_array.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -14,11 +14,12 @@
 
 #include <cstdint>
 #include <utility>
+#include <vector>
 
 // Size / dtype checks.
 struct DtypeCheck {
-    py::dtype numpy{};
-    py::dtype pybind11{};
+    py::dtype numpy;
+    py::dtype pybind11;
 };
 
 template <typename T>
@@ -43,11 +44,11 @@
 }
 
 struct DtypeSizeCheck {
-    std::string name{};
+    std::string name;
     int size_cpp{};
     int size_numpy{};
     // For debugging.
-    py::dtype dtype{};
+    py::dtype dtype;
 };
 
 template <typename T>
@@ -246,6 +247,22 @@
     sm.def("nbytes", [](const arr &a) { return a.nbytes(); });
     sm.def("owndata", [](const arr &a) { return a.owndata(); });
 
+#ifdef PYBIND11_HAS_SPAN
+    // test_shape_strides_span
+    sm.def("shape_span", [](const arr &a) {
+        auto span = a.shape_span();
+        return std::vector<py::ssize_t>(span.begin(), span.end());
+    });
+    sm.def("strides_span", [](const arr &a) {
+        auto span = a.strides_span();
+        return std::vector<py::ssize_t>(span.begin(), span.end());
+    });
+    // Test that spans can be used to construct new arrays
+    sm.def("array_from_spans", [](const arr &a) {
+        return py::array(a.dtype(), a.shape_span(), a.strides_span(), a.data(), a);
+    });
+#endif
+
     // test_index_offset
     def_index_fn(index_at, const arr &);
     def_index_fn(index_at_t, const arr_t &);
@@ -282,6 +299,7 @@
     struct ArrayClass {
         int data[2] = {1, 2};
         ArrayClass() { py::print("ArrayClass()"); }
+        ArrayClass(const ArrayClass &) = default;
         ~ArrayClass() { py::print("~ArrayClass()"); }
     };
     py::class_<ArrayClass>(sm, "ArrayClass")
diff -Nru pybind11-3.0.1/tests/test_numpy_array.py pybind11-3.0.4/tests/test_numpy_array.py
--- pybind11-3.0.1/tests/test_numpy_array.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_numpy_array.py	2026-04-19 06:01:51.000000000 +0300
@@ -68,6 +68,45 @@
     assert not m.owndata(a)
 
 
+@pytest.mark.skipif(not hasattr(m, "shape_span"), reason="std::span not available")
+def test_shape_strides_span():
+    # Test 0-dimensional array (scalar)
+    a = np.array(42, "f8")
+    assert m.ndim(a) == 0
+    assert m.shape_span(a) == []
+    assert m.strides_span(a) == []
+
+    # Test 1-dimensional array
+    a = np.array([1, 2, 3, 4], "u2")
+    assert m.ndim(a) == 1
+    assert m.shape_span(a) == [4]
+    assert m.strides_span(a) == [2]
+
+    # Test 2-dimensional array
+    a = np.array([[1, 2, 3], [4, 5, 6]], "u2").view()
+    a.flags.writeable = False
+    assert m.ndim(a) == 2
+    assert m.shape_span(a) == [2, 3]
+    assert m.strides_span(a) == [6, 2]
+
+    # Test 3-dimensional array
+    a = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], "i4")
+    assert m.ndim(a) == 3
+    assert m.shape_span(a) == [2, 2, 2]
+    # Verify spans match regular shape/strides
+    assert list(m.shape_span(a)) == list(m.shape(a))
+    assert list(m.strides_span(a)) == list(m.strides(a))
+
+    # Test that spans can be used to construct new arrays
+    original = np.array([[1, 2, 3], [4, 5, 6]], "f4")
+    new_array = m.array_from_spans(original)
+    assert new_array.shape == original.shape
+    assert new_array.strides == original.strides
+    assert new_array.dtype == original.dtype
+    # Verify data is shared (since we pass the same data pointer)
+    np.testing.assert_array_equal(new_array, original)
+
+
 @pytest.mark.parametrize(
     ("args", "ret"), [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)]
 )
diff -Nru pybind11-3.0.1/tests/test_numpy_dtypes.py pybind11-3.0.4/tests/test_numpy_dtypes.py
--- pybind11-3.0.1/tests/test_numpy_dtypes.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_numpy_dtypes.py	2026-04-19 06:01:51.000000000 +0300
@@ -367,7 +367,7 @@
 def test_signature(doc):
     assert (
         doc(m.create_rec_nested)
-        == "create_rec_nested(arg0: typing.SupportsInt) -> numpy.typing.NDArray[NestedStruct]"
+        == "create_rec_nested(arg0: typing.SupportsInt | typing.SupportsIndex) -> numpy.typing.NDArray[NestedStruct]"
     )
 
 
diff -Nru pybind11-3.0.1/tests/test_numpy_vectorize.cpp pybind11-3.0.4/tests/test_numpy_vectorize.cpp
--- pybind11-3.0.1/tests/test_numpy_vectorize.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_numpy_vectorize.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -78,6 +78,27 @@
     struct VectorizeTestClass {
         explicit VectorizeTestClass(int v) : value{v} {};
         float method(int x, float y) const { return y + (float) (x + value); }
+        // Exercises vectorize(Return (Class::*)(Args...) &)
+        // NOLINTNEXTLINE(readability-make-member-function-const)
+        float method_lref(int x, float y) & { return y + (float) (x + value); }
+        // Exercises vectorize(Return (Class::*)(Args...) const &)
+        float method_const_lref(int x, float y) const & { return y + (float) (x + value); }
+        // Exercises vectorize(Return (Class::*)(Args...) noexcept)
+        // NOLINTNEXTLINE(readability-make-member-function-const)
+        float method_noexcept(int x, float y) noexcept { return y + (float) (x + value); }
+        // Exercises vectorize(Return (Class::*)(Args...) const noexcept)
+        float method_const_noexcept(int x, float y) const noexcept {
+            return y + (float) (x + value);
+        }
+#ifdef __cpp_noexcept_function_type
+        // Exercises vectorize(Return (Class::*)(Args...) & noexcept)
+        // NOLINTNEXTLINE(readability-make-member-function-const)
+        float method_lref_noexcept(int x, float y) & noexcept { return y + (float) (x + value); }
+        // Exercises vectorize(Return (Class::*)(Args...) const & noexcept)
+        float method_const_lref_noexcept(int x, float y) const & noexcept {
+            return y + (float) (x + value);
+        }
+#endif
         int value = 0;
     };
     py::class_<VectorizeTestClass> vtc(m, "VectorizeTestClass");
@@ -85,6 +106,15 @@
 
     // Automatic vectorizing of methods
     vtc.def("method", py::vectorize(&VectorizeTestClass::method));
+    vtc.def("method_lref", py::vectorize(&VectorizeTestClass::method_lref));
+    vtc.def("method_const_lref", py::vectorize(&VectorizeTestClass::method_const_lref));
+    vtc.def("method_noexcept", py::vectorize(&VectorizeTestClass::method_noexcept));
+    vtc.def("method_const_noexcept", py::vectorize(&VectorizeTestClass::method_const_noexcept));
+#ifdef __cpp_noexcept_function_type
+    vtc.def("method_lref_noexcept", py::vectorize(&VectorizeTestClass::method_lref_noexcept));
+    vtc.def("method_const_lref_noexcept",
+            py::vectorize(&VectorizeTestClass::method_const_lref_noexcept));
+#endif
 
     // test_trivial_broadcasting
     // Internal optimization test for whether the input is trivially broadcastable:
diff -Nru pybind11-3.0.1/tests/test_numpy_vectorize.py pybind11-3.0.4/tests/test_numpy_vectorize.py
--- pybind11-3.0.1/tests/test_numpy_vectorize.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_numpy_vectorize.py	2026-04-19 06:01:51.000000000 +0300
@@ -2,6 +2,7 @@
 
 import pytest
 
+from pybind11_tests import defined___cpp_noexcept_function_type
 from pybind11_tests import numpy_vectorize as m
 
 np = pytest.importorskip("numpy")
@@ -211,11 +212,11 @@
         "vec_passthrough("
         + ", ".join(
             [
-                "arg0: typing.SupportsFloat",
+                "arg0: typing.SupportsFloat | typing.SupportsIndex",
                 "arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
                 "arg2: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
                 "arg3: typing.Annotated[numpy.typing.ArrayLike, numpy.int32]",
-                "arg4: typing.SupportsInt",
+                "arg4: typing.SupportsInt | typing.SupportsIndex",
                 "arg5: m.numpy_vectorize.NonPODClass",
                 "arg6: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
             ]
@@ -246,6 +247,56 @@
     assert np.all(o.method(x, y) == [[14, 15], [24, 25]])
 
 
+def test_ref_qualified_method_vectorization():
+    """Test issue #2234 follow-up: vectorize with lvalue-ref-qualified member pointers.
+
+    Covers:
+      - vectorize(Return (Class::*)(Args...) &)
+      - vectorize(Return (Class::*)(Args...) const &)
+      - vectorize(Return (Class::*)(Args...) & noexcept)
+      - vectorize(Return (Class::*)(Args...) const & noexcept)
+    """
+    o = m.VectorizeTestClass(3)
+    x = np.array([1, 2], dtype="int")
+    y = np.array([[10], [20]], dtype="float32")
+    assert np.all(o.method_lref(x, y) == [[14, 15], [24, 25]])
+    assert np.all(o.method_const_lref(x, y) == [[14, 15], [24, 25]])
+
+
+@pytest.mark.skipif(
+    not defined___cpp_noexcept_function_type,
+    reason="Requires __cpp_noexcept_function_type",
+)
+def test_noexcept_ref_qualified_method_vectorization():
+    """Test issue #2234 follow-up: vectorize with noexcept lvalue-ref-qualified member pointers.
+
+    Covers:
+      - vectorize(Return (Class::*)(Args...) & noexcept)
+      - vectorize(Return (Class::*)(Args...) const & noexcept)
+    """
+    o = m.VectorizeTestClass(3)
+    x = np.array([1, 2], dtype="int")
+    y = np.array([[10], [20]], dtype="float32")
+    assert np.all(o.method_lref_noexcept(x, y) == [[14, 15], [24, 25]])
+    assert np.all(o.method_const_lref_noexcept(x, y) == [[14, 15], [24, 25]])
+
+
+def test_noexcept_method_vectorization():
+    """Test issue #2234: vectorize must handle noexcept member function pointers.
+
+    Covers both new vectorize specialisations:
+      - vectorize(Return (Class::*)(Args...) noexcept)
+      - vectorize(Return (Class::*)(Args...) const noexcept)
+    """
+    o = m.VectorizeTestClass(3)
+    x = np.array([1, 2], dtype="int")
+    y = np.array([[10], [20]], dtype="float32")
+    # vectorize(Return (Class::*)(Args...) noexcept)
+    assert np.all(o.method_noexcept(x, y) == [[14, 15], [24, 25]])
+    # vectorize(Return (Class::*)(Args...) const noexcept)
+    assert np.all(o.method_const_noexcept(x, y) == [[14, 15], [24, 25]])
+
+
 def test_array_collapse():
     assert not isinstance(m.vectorized_func(1, 2, 3), np.ndarray)
     assert not isinstance(m.vectorized_func(np.array(1), 2, 3), np.ndarray)
diff -Nru pybind11-3.0.1/tests/test_potentially_slicing_weak_ptr.cpp pybind11-3.0.4/tests/test_potentially_slicing_weak_ptr.cpp
--- pybind11-3.0.1/tests/test_potentially_slicing_weak_ptr.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_potentially_slicing_weak_ptr.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -11,7 +11,9 @@
 
 template <int> // Using int as a trick to easily generate multiple types.
 struct VirtBase {
+    VirtBase() = default;
     virtual ~VirtBase() = default;
+    VirtBase(const VirtBase &) = delete;
     virtual int get_code() { return 100; }
 };
 
diff -Nru pybind11-3.0.1/tests/test_pytorch_shared_ptr_cast_regression.cpp pybind11-3.0.4/tests/test_pytorch_shared_ptr_cast_regression.cpp
--- pybind11-3.0.1/tests/test_pytorch_shared_ptr_cast_regression.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_pytorch_shared_ptr_cast_regression.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,62 @@
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+
+#if defined(__clang__)
+#    if __has_warning("-Wdeprecated-copy-with-user-provided-dtor")
+#        pragma clang diagnostic error "-Wdeprecated-copy-with-user-provided-dtor"
+#    endif
+#    if __has_warning("-Wdeprecated-copy-with-dtor")
+#        pragma clang diagnostic error "-Wdeprecated-copy-with-dtor"
+#    endif
+#endif
+
+namespace test_pytorch_regressions {
+
+// Directly extracted from PyTorch patterns that regressed in CI.
+struct TracingState : std::enable_shared_from_this<TracingState> {
+    TracingState() = default;
+    ~TracingState() = default;
+    int value = 0;
+};
+
+const std::shared_ptr<TracingState> &get_tracing_state() {
+    static std::shared_ptr<TracingState> state = std::make_shared<TracingState>();
+    return state;
+}
+
+struct InterfaceType {
+    ~InterfaceType() = default;
+    int value = 0;
+};
+using InterfaceTypePtr = std::shared_ptr<InterfaceType>;
+
+struct CompilationUnit {
+    InterfaceTypePtr iface = std::make_shared<InterfaceType>();
+
+    InterfaceTypePtr get_interface(const std::string &) const { return iface; }
+};
+
+} // namespace test_pytorch_regressions
+
+TEST_SUBMODULE(pybind11_pytorch_regressions, m) {
+    using namespace test_pytorch_regressions;
+
+    py::class_<TracingState, std::shared_ptr<TracingState>>(m, "TracingState")
+        .def(py::init<>())
+        .def_readwrite("value", &TracingState::value);
+
+    m.def("_get_tracing_state", []() { return get_tracing_state(); });
+
+    py::class_<InterfaceType, InterfaceTypePtr>(m, "InterfaceType")
+        .def(py::init<>())
+        .def_readwrite("value", &InterfaceType::value);
+
+    py::class_<CompilationUnit, std::shared_ptr<CompilationUnit>>(m, "CompilationUnit")
+        .def(py::init<>())
+        .def("get_interface",
+             [](const std::shared_ptr<CompilationUnit> &self, const std::string &name) {
+                 return self->get_interface(name);
+             });
+}
diff -Nru pybind11-3.0.1/tests/test_pytorch_shared_ptr_cast_regression.py pybind11-3.0.4/tests/test_pytorch_shared_ptr_cast_regression.py
--- pybind11-3.0.1/tests/test_pytorch_shared_ptr_cast_regression.py	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_pytorch_shared_ptr_cast_regression.py	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from pybind11_tests import pybind11_pytorch_regressions as m
+
+
+def test_pytorch_like_get_tracing_state_aliases_singleton_shared_ptr():
+    a = m._get_tracing_state()
+    b = m._get_tracing_state()
+
+    a.value = 17
+
+    assert b.value == 17
+    assert m._get_tracing_state().value == 17
+
+
+def test_pytorch_like_compilation_unit_get_interface_aliases_member_shared_ptr():
+    cu = m.CompilationUnit()
+
+    a = cu.get_interface("iface")
+    b = cu.get_interface("iface")
+
+    a.value = 23
+
+    assert b.value == 23
+    assert cu.get_interface("iface").value == 23
diff -Nru pybind11-3.0.1/tests/test_pytypes.cpp pybind11-3.0.4/tests/test_pytypes.cpp
--- pybind11-3.0.1/tests/test_pytypes.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_pytypes.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -209,6 +209,7 @@
     m.def("get_tuple_from_iterable", [](const py::iterable &iter) { return py::tuple(iter); });
     // test_float
     m.def("get_float", [] { return py::float_(0.0f); });
+    m.def("float_roundtrip", [](py::float_ f) { return f; });
     // test_list
     m.def("list_no_args", []() { return py::list{}; });
     m.def("list_ssize_t", []() { return py::list{(py::ssize_t) 0}; });
@@ -1085,8 +1086,7 @@
     static_class.def(py::init());
     static_class.attr_with_type_hint<py::typing::ClassVar<float>>("x") = 1.0;
     static_class.attr_with_type_hint<py::typing::ClassVar<py::typing::Dict<py::str, int>>>(
-        "dict_str_int")
-        = py::dict();
+        "dict_str_int") = py::dict();
 
     struct Instance {};
     auto instance = py::class_<Instance>(m, "Instance", py::dynamic_attr());
@@ -1210,4 +1210,6 @@
     m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs<RealNumber> {
         return py::isinstance<RealNumber>(x);
     });
+
+    m.def("const_kwargs_ref_to_str", [](const py::kwargs &kwargs) { return py::str(kwargs); });
 }
diff -Nru pybind11-3.0.1/tests/test_pytypes.py pybind11-3.0.4/tests/test_pytypes.py
--- pybind11-3.0.1/tests/test_pytypes.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_pytypes.py	2026-04-19 06:01:51.000000000 +0300
@@ -61,6 +61,13 @@
 
 def test_float(doc):
     assert doc(m.get_float) == "get_float() -> float"
+    assert doc(m.float_roundtrip) == "float_roundtrip(arg0: float) -> float"
+    f1 = m.float_roundtrip(5.5)
+    assert isinstance(f1, float)
+    assert f1 == 5.5
+    f2 = m.float_roundtrip(5)
+    assert isinstance(f2, float)
+    assert f2 == 5.0
 
 
 def test_list(capture, doc):
@@ -917,7 +924,7 @@
 def test_tuple_nonempty_annotations(doc):
     assert (
         doc(m.annotate_tuple_float_str)
-        == "annotate_tuple_float_str(arg0: tuple[typing.SupportsFloat, str]) -> None"
+        == "annotate_tuple_float_str(arg0: tuple[float, str]) -> None"
     )
 
 
@@ -930,21 +937,21 @@
 def test_tuple_variable_length_annotations(doc):
     assert (
         doc(m.annotate_tuple_variable_length)
-        == "annotate_tuple_variable_length(arg0: tuple[typing.SupportsFloat, ...]) -> None"
+        == "annotate_tuple_variable_length(arg0: tuple[float, ...]) -> None"
     )
 
 
 def test_dict_annotations(doc):
     assert (
         doc(m.annotate_dict_str_int)
-        == "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt]) -> None"
+        == "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt | typing.SupportsIndex]) -> None"
     )
 
 
 def test_list_annotations(doc):
     assert (
         doc(m.annotate_list_int)
-        == "annotate_list_int(arg0: list[typing.SupportsInt]) -> None"
+        == "annotate_list_int(arg0: list[typing.SupportsInt | typing.SupportsIndex]) -> None"
     )
 
 
@@ -962,7 +969,7 @@
 def test_iterator_annotations(doc):
     assert (
         doc(m.annotate_iterator_int)
-        == "annotate_iterator_int(arg0: collections.abc.Iterator[typing.SupportsInt]) -> None"
+        == "annotate_iterator_int(arg0: collections.abc.Iterator[typing.SupportsInt | typing.SupportsIndex]) -> None"
     )
 
 
@@ -982,14 +989,15 @@
 
 def test_type_annotation(doc):
     assert (
-        doc(m.annotate_type) == "annotate_type(arg0: type[typing.SupportsInt]) -> type"
+        doc(m.annotate_type)
+        == "annotate_type(arg0: type[typing.SupportsInt | typing.SupportsIndex]) -> type"
     )
 
 
 def test_union_annotations(doc):
     assert (
         doc(m.annotate_union)
-        == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]"
+        == "annotate_union(arg0: list[str | int | object], arg1: str, arg2: int, arg3: object) -> list[str | int | object]"
     )
 
 
@@ -1000,7 +1008,7 @@
 def test_union_object_annotations(doc):
     assert (
         doc(m.annotate_union_to_object)
-        == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object"
+        == "annotate_union_to_object(arg0: typing.SupportsInt | typing.SupportsIndex | str) -> object"
     )
 
 
@@ -1037,7 +1045,7 @@
 def test_optional_object_annotations(doc):
     assert (
         doc(m.annotate_optional_to_object)
-        == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object"
+        == "annotate_optional_to_object(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> object"
     )
 
 
@@ -1160,7 +1168,10 @@
 def test_module_attribute_types() -> None:
     module_annotations = get_annotations_helper(m)
 
-    assert module_annotations["list_int"] == "list[typing.SupportsInt]"
+    assert (
+        module_annotations["list_int"]
+        == "list[typing.SupportsInt | typing.SupportsIndex]"
+    )
     assert module_annotations["set_str"] == "set[str]"
     assert module_annotations["foo"] == "pybind11_tests.pytypes.foo"
 
@@ -1183,7 +1194,10 @@
 
     module_annotations = get_annotations(m)
 
-    assert module_annotations["list_int"] == "list[typing.SupportsInt]"
+    assert (
+        module_annotations["list_int"]
+        == "list[typing.SupportsInt | typing.SupportsIndex]"
+    )
     assert module_annotations["set_str"] == "set[str]"
 
 
@@ -1197,10 +1211,13 @@
     instance_annotations = get_annotations_helper(m.Instance)
 
     assert empty_annotations is None
-    assert static_annotations["x"] == "typing.ClassVar[typing.SupportsFloat]"
+    assert (
+        static_annotations["x"]
+        == "typing.ClassVar[typing.SupportsFloat | typing.SupportsIndex]"
+    )
     assert (
         static_annotations["dict_str_int"]
-        == "typing.ClassVar[dict[str, typing.SupportsInt]]"
+        == "typing.ClassVar[dict[str, typing.SupportsInt | typing.SupportsIndex]]"
     )
 
     assert m.Static.x == 1.0
@@ -1212,7 +1229,7 @@
     static.dict_str_int["hi"] = 3
     assert m.Static().dict_str_int == {"hi": 3}
 
-    assert instance_annotations["y"] == "typing.SupportsFloat"
+    assert instance_annotations["y"] == "typing.SupportsFloat | typing.SupportsIndex"
     instance1 = m.Instance()
     instance1.y = 4.0
 
@@ -1229,7 +1246,10 @@
 def test_redeclaration_attr_with_type_hint() -> None:
     obj = m.Instance()
     m.attr_with_type_hint_float_x(obj)
-    assert get_annotations_helper(obj)["x"] == "typing.SupportsFloat"
+    assert (
+        get_annotations_helper(obj)["x"]
+        == "typing.SupportsFloat | typing.SupportsIndex"
+    )
     with pytest.raises(
         RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'
     ):
@@ -1347,3 +1367,8 @@
         backport_typehints(doc(m.check_type_guard))
         == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]"
     )
+
+
+def test_const_kwargs_ref_to_str():
+    assert m.const_kwargs_ref_to_str() == "{}"
+    assert m.const_kwargs_ref_to_str(a=1) == "{'a': 1}"
diff -Nru pybind11-3.0.1/tests/test_scoped_critical_section.cpp pybind11-3.0.4/tests/test_scoped_critical_section.cpp
--- pybind11-3.0.1/tests/test_scoped_critical_section.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_scoped_critical_section.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -7,8 +7,7 @@
 #include <thread>
 #include <utility>
 
-#if defined(PYBIND11_CPP20) && defined(__has_include) && __has_include(<barrier>)
-#    define PYBIND11_HAS_BARRIER 1
+#if defined(PYBIND11_HAS_STD_BARRIER)
 #    include <barrier>
 #endif
 
@@ -39,7 +38,7 @@
     std::atomic_bool value_{false};
 };
 
-#if defined(PYBIND11_HAS_BARRIER)
+#if defined(PYBIND11_HAS_STD_BARRIER)
 
 // Modifying the C/C++ members of a Python object from multiple threads requires a critical section
 // to ensure thread safety and data integrity.
@@ -259,7 +258,7 @@
     (void) BoolWrapperHandle.ptr(); // suppress unused variable warning
 
     m.attr("has_barrier") =
-#ifdef PYBIND11_HAS_BARRIER
+#ifdef PYBIND11_HAS_STD_BARRIER
         true;
 #else
         false;
diff -Nru pybind11-3.0.1/tests/test_sequences_and_iterators.cpp pybind11-3.0.4/tests/test_sequences_and_iterators.cpp
--- pybind11-3.0.1/tests/test_sequences_and_iterators.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_sequences_and_iterators.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -556,7 +556,7 @@
     });
 
     m.def("count_nonzeros", [](const py::dict &d) {
-        return std::count_if(d.begin(), d.end(), [](std::pair<py::handle, py::handle> p) {
+        return std::count_if(d.begin(), d.end(), [](const std::pair<py::handle, py::handle> &p) {
             return p.second.cast<int>() != 0;
         });
     });
diff -Nru pybind11-3.0.1/tests/test_smart_ptr.cpp pybind11-3.0.4/tests/test_smart_ptr.cpp
--- pybind11-3.0.1/tests/test_smart_ptr.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_smart_ptr.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -161,6 +161,8 @@
         print_created(this);
         pointer_set<MyObject4a>().insert(this);
     };
+    MyObject4a(const MyObject4a &) = delete;
+
     int value;
 
     static void cleanupAllInstances() {
@@ -182,6 +184,7 @@
 class MyObject4b : public MyObject4a {
 public:
     explicit MyObject4b(int i) : MyObject4a(i) { print_created(this); }
+    MyObject4b(const MyObject4b &) = delete;
     ~MyObject4b() override { print_destroyed(this); }
 };
 
@@ -189,6 +192,7 @@
 class MyObject5 { // managed by huge_unique_ptr
 public:
     explicit MyObject5(int value) : value{value} { print_created(this); }
+    MyObject5(const MyObject5 &) = delete;
     ~MyObject5() { print_destroyed(this); }
     int value;
 };
@@ -216,7 +220,7 @@
         ~A() { print_destroyed(this); }
     };
 
-    A value = {};
+    A value;
     std::shared_ptr<A> shared = std::make_shared<A>();
 };
 
@@ -224,13 +228,14 @@
 struct SharedFromThisRef {
     struct B : std::enable_shared_from_this<B> {
         B() { print_created(this); }
-        // NOLINTNEXTLINE(bugprone-copy-constructor-init)
+        // NOLINTNEXTLINE(bugprone-copy-constructor-init, readability-redundant-member-init)
         B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
+        // NOLINTNEXTLINE(readability-redundant-member-init)
         B(B &&) noexcept : std::enable_shared_from_this<B>() { print_move_created(this); }
         ~B() { print_destroyed(this); }
     };
 
-    B value = {};
+    B value;
     std::shared_ptr<B> shared = std::make_shared<B>();
 };
 
@@ -242,9 +247,32 @@
 };
 struct SharedFromThisVirt : virtual SharedFromThisVBase {};
 
+// Issue #5989: static_pointer_cast where dynamic_pointer_cast is needed
+// (virtual inheritance with shared_ptr holder)
+struct SftVirtBase : std::enable_shared_from_this<SftVirtBase> {
+    SftVirtBase() = default;
+    virtual ~SftVirtBase() = default;
+    static std::shared_ptr<SftVirtBase> create() { return std::make_shared<SftVirtBase>(); }
+    virtual std::string name() { return "SftVirtBase"; }
+};
+struct SftVirtDerived : SftVirtBase {
+    using SftVirtBase::SftVirtBase;
+    static std::shared_ptr<SftVirtDerived> create() { return std::make_shared<SftVirtDerived>(); }
+    std::string name() override { return "SftVirtDerived"; }
+};
+struct SftVirtDerived2 : virtual SftVirtDerived {
+    using SftVirtDerived::SftVirtDerived;
+    static std::shared_ptr<SftVirtDerived2> create() {
+        return std::make_shared<SftVirtDerived2>();
+    }
+    std::string name() override { return "SftVirtDerived2"; }
+    std::string call_name(const std::shared_ptr<SftVirtDerived2> &d2) { return d2->name(); }
+};
+
 // test_move_only_holder
 struct C {
     C() { print_created(this); }
+    C(const C &) = delete;
     ~C() { print_destroyed(this); }
 };
 
@@ -265,6 +293,7 @@
 // test_move_only_holder_with_addressof_operator
 struct TypeForMoveOnlyHolderWithAddressOf {
     explicit TypeForMoveOnlyHolderWithAddressOf(int value) : value{value} { print_created(this); }
+    TypeForMoveOnlyHolderWithAddressOf(const TypeForMoveOnlyHolderWithAddressOf &) = delete;
     ~TypeForMoveOnlyHolderWithAddressOf() { print_destroyed(this); }
     std::string toString() const {
         return "MoveOnlyHolderWithAddressOf[" + std::to_string(value) + "]";
@@ -515,6 +544,17 @@
     py::class_<SharedFromThisVirt, std::shared_ptr<SharedFromThisVirt>>(m, "SharedFromThisVirt")
         .def_static("get", []() { return sft.get(); });
 
+    // Issue #5989: static_pointer_cast where dynamic_pointer_cast is needed
+    py::class_<SftVirtBase, std::shared_ptr<SftVirtBase>>(m, "SftVirtBase")
+        .def(py::init<>(&SftVirtBase::create))
+        .def("name", &SftVirtBase::name);
+    py::class_<SftVirtDerived, SftVirtBase, std::shared_ptr<SftVirtDerived>>(m, "SftVirtDerived")
+        .def(py::init<>(&SftVirtDerived::create));
+    py::class_<SftVirtDerived2, SftVirtDerived, std::shared_ptr<SftVirtDerived2>>(
+        m, "SftVirtDerived2")
+        .def(py::init<>(&SftVirtDerived2::create))
+        .def("call_name", &SftVirtDerived2::call_name, py::arg("d2"));
+
     // test_move_only_holder
     py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
         .def_static("make", []() { return custom_unique_ptr<C>(new C); })
diff -Nru pybind11-3.0.1/tests/test_smart_ptr.py pybind11-3.0.4/tests/test_smart_ptr.py
--- pybind11-3.0.1/tests/test_smart_ptr.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_smart_ptr.py	2026-04-19 06:01:51.000000000 +0300
@@ -251,6 +251,19 @@
     assert y is z
 
 
+def test_shared_from_this_virt_shared_ptr_arg():
+    """Issue #5989: static_pointer_cast fails with virtual inheritance."""
+    b = m.SftVirtBase()
+    assert b.name() == "SftVirtBase"
+
+    d = m.SftVirtDerived()
+    assert d.name() == "SftVirtDerived"
+
+    d2 = m.SftVirtDerived2()
+    assert d2.name() == "SftVirtDerived2"
+    assert d2.call_name(d2) == "SftVirtDerived2"
+
+
 @pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
 def test_move_only_holder():
     a = m.TypeWithMoveOnlyHolder.make()
diff -Nru pybind11-3.0.1/tests/test_standalone_enum_module.py pybind11-3.0.4/tests/test_standalone_enum_module.py
--- pybind11-3.0.1/tests/test_standalone_enum_module.py	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_standalone_enum_module.py	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+import os
+
+import env
+
+
+def test_enum_import_exit_no_crash():
+    # Added in PR #6015. Modeled after reproducer under issue #5976
+    env.check_script_success_in_subprocess(
+        f"""
+        import sys
+        sys.path.insert(0, {os.path.dirname(env.__file__)!r})
+        import standalone_enum_module as m
+        assert m.SomeEnum.__class__.__name__ == "pybind11_type"
+        """,
+        rerun=1,
+    )
diff -Nru pybind11-3.0.1/tests/test_stl_binders.cpp pybind11-3.0.4/tests/test_stl_binders.cpp
--- pybind11-3.0.1/tests/test_stl_binders.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_stl_binders.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -55,7 +55,7 @@
 Map *times_ten(int n) {
     auto *m = new Map();
     for (int i = 1; i <= n; i++) {
-        m->emplace(int(i), E_nc(10 * i));
+        m->emplace(i, E_nc(10 * i));
     }
     return m;
 }
@@ -65,7 +65,7 @@
     auto *m = new NestMap();
     for (int i = 1; i <= n; i++) {
         for (int j = 1; j <= n; j++) {
-            (*m)[i].emplace(int(j * 10), E_nc(100 * j));
+            (*m)[i].emplace(j * 10, E_nc(100 * j));
         }
     }
     return m;
diff -Nru pybind11-3.0.1/tests/test_stl_binders.py pybind11-3.0.4/tests/test_stl_binders.py
--- pybind11-3.0.1/tests/test_stl_binders.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_stl_binders.py	2026-04-19 06:01:51.000000000 +0300
@@ -258,7 +258,7 @@
             assert nvnc[i][j].value == j + 1
 
     # Note: maps do not have .values()
-    for _, v in nvnc.items():
+    for v in nvnc.values():
         for i, j in enumerate(v, start=1):
             assert j.value == i
 
@@ -269,7 +269,7 @@
             assert nmnc[i][j].value == 10 * j
 
     vsum = 0
-    for _, v_o in nmnc.items():
+    for v_o in nmnc.values():
         for k_i, v_i in v_o.items():
             assert v_i.value == 10 * k_i
             vsum += v_i.value
@@ -283,7 +283,7 @@
             assert numnc[i][j].value == 10 * j
 
     vsum = 0
-    for _, v_o in numnc.items():
+    for v_o in numnc.values():
         for k_i, v_i in v_o.items():
             assert v_i.value == 10 * k_i
             vsum += v_i.value
diff -Nru pybind11-3.0.1/tests/test_stl.cpp pybind11-3.0.4/tests/test_stl.cpp
--- pybind11-3.0.1/tests/test_stl.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_stl.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -99,6 +99,7 @@
     using OptionalEnumValue = OptionalImpl<EnumType>;
 
     OptionalProperties() : value(EnumType::kSet) {}
+    OptionalProperties(const OptionalProperties &) = default;
     ~OptionalProperties() {
         // Reset value to detect use-after-destruction.
         // This is set to a specific value rather than nullopt to ensure that
diff -Nru pybind11-3.0.1/tests/test_stl.py pybind11-3.0.4/tests/test_stl.py
--- pybind11-3.0.1/tests/test_stl.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_stl.py	2026-04-19 06:01:51.000000000 +0300
@@ -22,7 +22,7 @@
     assert doc(m.cast_vector) == "cast_vector() -> list[int]"
     assert (
         doc(m.load_vector)
-        == "load_vector(arg0: collections.abc.Sequence[typing.SupportsInt]) -> bool"
+        == "load_vector(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> bool"
     )
 
     # Test regression caused by 936: pointers to stl containers weren't castable
@@ -51,7 +51,7 @@
     )
     assert (
         doc(m.load_array)
-        == 'load_array(arg0: typing.Annotated[collections.abc.Sequence[typing.SupportsInt], "FixedSize(2)"]) -> bool'
+        == 'load_array(arg0: typing.Annotated[collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], "FixedSize(2)"]) -> bool'
     )
 
 
@@ -72,7 +72,7 @@
     assert doc(m.cast_valarray) == "cast_valarray() -> list[int]"
     assert (
         doc(m.load_valarray)
-        == "load_valarray(arg0: collections.abc.Sequence[typing.SupportsInt]) -> bool"
+        == "load_valarray(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> bool"
     )
 
 
@@ -234,7 +234,7 @@
 
     assert (
         doc(m.double_or_zero_refsensitive)
-        == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int"
+        == "double_or_zero_refsensitive(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> int"
     )
 
     assert m.half_or_none_refsensitive(0) is None
@@ -352,7 +352,7 @@
 
     assert (
         doc(m.load_variant)
-        == "load_variant(arg0: typing.SupportsInt | str | typing.SupportsFloat | None) -> str"
+        == "load_variant(arg0: typing.SupportsInt | typing.SupportsIndex | str | typing.SupportsFloat | typing.SupportsIndex | None) -> str"
     )
 
 
@@ -368,7 +368,7 @@
 
     assert (
         doc(m.load_monostate_variant)
-        == "load_monostate_variant(arg0: None | typing.SupportsInt | str) -> str"
+        == "load_monostate_variant(arg0: None | typing.SupportsInt | typing.SupportsIndex | str) -> str"
     )
 
 
@@ -388,7 +388,7 @@
         msg(excinfo.value)
         == """
         stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
-            1. (v: collections.abc.Sequence[typing.SupportsInt] = None) -> list[int]
+            1. (v: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = None) -> list[int]
 
         Invoked with:
     """
@@ -400,7 +400,7 @@
         msg(excinfo.value)
         == """
         stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
-            1. (v: collections.abc.Sequence[typing.SupportsInt] = None) -> list[int]
+            1. (v: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = None) -> list[int]
 
         Invoked with: None
     """
@@ -615,7 +615,7 @@
     # convert mode
     assert (
         doc(m.roundtrip_std_vector_int)
-        == "roundtrip_std_vector_int(arg0: collections.abc.Sequence[typing.SupportsInt]) -> list[int]"
+        == "roundtrip_std_vector_int(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> list[int]"
     )
     assert m.roundtrip_std_vector_int([1, 2, 3]) == [1, 2, 3]
     assert m.roundtrip_std_vector_int((1, 2, 3)) == [1, 2, 3]
@@ -668,7 +668,7 @@
     # convert mode
     assert (
         doc(m.roundtrip_std_map_str_int)
-        == "roundtrip_std_map_str_int(arg0: collections.abc.Mapping[str, typing.SupportsInt]) -> dict[str, int]"
+        == "roundtrip_std_map_str_int(arg0: collections.abc.Mapping[str, typing.SupportsInt | typing.SupportsIndex]) -> dict[str, int]"
     )
     assert m.roundtrip_std_map_str_int(a1b2c3) == a1b2c3
     assert m.roundtrip_std_map_str_int(FormalMappingLike(**a1b2c3)) == a1b2c3
@@ -714,7 +714,7 @@
     # convert mode
     assert (
         doc(m.roundtrip_std_set_int)
-        == "roundtrip_std_set_int(arg0: collections.abc.Set[typing.SupportsInt]) -> set[int]"
+        == "roundtrip_std_set_int(arg0: collections.abc.Set[typing.SupportsInt | typing.SupportsIndex]) -> set[int]"
     )
     assert m.roundtrip_std_set_int({1, 2, 3}) == {1, 2, 3}
     assert m.roundtrip_std_set_int(FormalSetLike(1, 2, 3)) == {1, 2, 3}
diff -Nru pybind11-3.0.1/tests/test_tagbased_polymorphic.cpp pybind11-3.0.4/tests/test_tagbased_polymorphic.cpp
--- pybind11-3.0.1/tests/test_tagbased_polymorphic.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_tagbased_polymorphic.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -17,6 +17,8 @@
     // (https://github.com/pybind/pybind11/pull/2016/).
     virtual ~Animal() = default;
 
+    Animal(const Animal &) = delete;
+
     // Enum for tag-based polymorphism.
     enum class Kind {
         Unknown = 0,
diff -Nru pybind11-3.0.1/tests/test_thread.cpp pybind11-3.0.4/tests/test_thread.cpp
--- pybind11-3.0.1/tests/test_thread.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_thread.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -15,6 +15,10 @@
 #include <chrono>
 #include <thread>
 
+#if defined(PYBIND11_HAS_STD_BARRIER)
+#    include <barrier>
+#endif
+
 namespace py = pybind11;
 
 namespace {
@@ -34,7 +38,6 @@
 } // namespace
 
 TEST_SUBMODULE(thread, m) {
-
     py::class_<IntStruct>(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); }));
 
     // implicitly_convertible uses loader_life_support when an implicit
@@ -67,6 +70,39 @@
     py::class_<EmptyStruct>(m, "EmptyStruct")
         .def_readonly_static("SharedInstance", &SharedInstance);
 
+#if defined(PYBIND11_HAS_STD_BARRIER)
+    // In the free-threaded build, during PyThreadState_Clear, removing the thread from the biased
+    // reference counting table may call destructors. Make sure that it doesn't crash.
+    m.def("test_pythread_state_clear_destructor", [](py::type cls) {
+        py::handle obj;
+
+        std::barrier barrier{2};
+        std::thread thread1{[&]() {
+            py::gil_scoped_acquire gil;
+            obj = cls().release();
+            barrier.arrive_and_wait();
+        }};
+        std::thread thread2{[&]() {
+            py::gil_scoped_acquire gil;
+            barrier.arrive_and_wait();
+            // ob_ref_shared becomes negative; transition to the queued state
+            obj.dec_ref();
+        }};
+
+        // jthread is not supported by Apple Clang
+        thread1.join();
+        thread2.join();
+    });
+#endif
+
+    m.attr("defined_PYBIND11_HAS_STD_BARRIER") =
+#ifdef PYBIND11_HAS_STD_BARRIER
+        true;
+#else
+        false;
+#endif
+    m.def("acquire_gil", []() { py::gil_scoped_acquire gil_acquired; });
+
     // NOTE: std::string_view also uses loader_life_support to ensure that
     // the string contents remain alive, but that's a C++ 17 feature.
 }
diff -Nru pybind11-3.0.1/tests/test_thread.py pybind11-3.0.4/tests/test_thread.py
--- pybind11-3.0.1/tests/test_thread.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_thread.py	2026-04-19 06:01:51.000000000 +0300
@@ -5,6 +5,7 @@
 
 import pytest
 
+import env
 from pybind11_tests import thread as m
 
 
@@ -66,3 +67,14 @@
         thread.start()
     for thread in threads:
         thread.join()
+
+
+@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
+@pytest.mark.skipif(not m.defined_PYBIND11_HAS_STD_BARRIER, reason="no <barrier>")
+@pytest.mark.skipif(env.sys_is_gil_enabled(), reason="Deadlock with the GIL")
+def test_pythread_state_clear_destructor():
+    class Foo:
+        def __del__(self):
+            m.acquire_gil()
+
+    m.test_pythread_state_clear_destructor(Foo)
diff -Nru pybind11-3.0.1/tests/test_type_caster_pyobject_ptr.py pybind11-3.0.4/tests/test_type_caster_pyobject_ptr.py
--- pybind11-3.0.1/tests/test_type_caster_pyobject_ptr.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_type_caster_pyobject_ptr.py	2026-04-19 06:01:51.000000000 +0300
@@ -103,7 +103,8 @@
 
 def test_type_caster_name_via_incompatible_function_arguments_type_error():
     with pytest.raises(
-        TypeError, match=r"1\. \(arg0: object, arg1: typing.SupportsInt\) -> None"
+        TypeError,
+        match=r"1\. \(arg0: object, arg1: typing.SupportsInt \| typing.SupportsIndex\) -> None",
     ):
         m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202))
 
diff -Nru pybind11-3.0.1/tests/test_unnamed_namespace_a.py pybind11-3.0.4/tests/test_unnamed_namespace_a.py
--- pybind11-3.0.1/tests/test_unnamed_namespace_a.py	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_unnamed_namespace_a.py	2026-04-19 06:01:51.000000000 +0300
@@ -5,7 +5,10 @@
 from pybind11_tests import unnamed_namespace_a as m
 from pybind11_tests import unnamed_namespace_b as mb
 
-XFAIL_CONDITION = "not m.defined_WIN32_or__WIN32 and (m.defined___clang__ or m.defined__LIBCPP_VERSION)"
+XFAIL_CONDITION = (
+    "m.defined__LIBCPP_VERSION or "
+    "(not m.defined_WIN32_or__WIN32 and m.defined___clang__)"
+)
 XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319"
 
 
diff -Nru pybind11-3.0.1/tests/test_virtual_functions.cpp pybind11-3.0.4/tests/test_virtual_functions.cpp
--- pybind11-3.0.1/tests/test_virtual_functions.cpp	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tests/test_virtual_functions.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -173,8 +173,7 @@
     using DataVisitor = std::function<void(const Data &)>;
 
     virtual void
-    operator()(const Data &first, const Data &second, const DataVisitor &visitor) const
-        = 0;
+    operator()(const Data &first, const Data &second, const DataVisitor &visitor) const = 0;
     virtual ~AdderBase() = default;
     AdderBase() = default;
     AdderBase(const AdderBase &) = delete;
diff -Nru pybind11-3.0.1/tests/test_with_catch/catch.cpp pybind11-3.0.4/tests/test_with_catch/catch.cpp
--- pybind11-3.0.1/tests/test_with_catch/catch.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/catch.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,175 @@
+// The Catch implementation is compiled here. This is a standalone
+// translation unit to avoid recompiling it for every test change.
+
+#include <pybind11/embed.h>
+
+#include <chrono>
+#include <csignal>
+#include <cstring>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+
+#ifndef _WIN32
+#    include <unistd.h>
+#endif
+
+// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
+// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
+PYBIND11_WARNING_DISABLE_MSVC(4996)
+
+// Catch uses _ internally, which breaks gettext style defines
+#ifdef _
+#    undef _
+#endif
+
+#define CATCH_CONFIG_RUNNER
+#define CATCH_CONFIG_DEFAULT_REPORTER "progress"
+#include "catch_skip.h"
+
+#include <catch.hpp>
+
+namespace py = pybind11;
+
+// Simple progress reporter that prints a line per test case.
+namespace {
+
+class ProgressReporter : public Catch::StreamingReporterBase<ProgressReporter> {
+public:
+    using StreamingReporterBase<ProgressReporter>::StreamingReporterBase;
+
+    static std::string getDescription() { return "Simple progress reporter (one line per test)"; }
+
+    void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
+        print_python_version_once();
+        auto &os = Catch::cout();
+        os << "[ RUN      ] " << testInfo.name << '\n';
+        os.flush();
+    }
+
+    void testCaseEnded(Catch::TestCaseStats const &stats) override {
+        bool failed = stats.totals.assertions.failed > 0;
+        auto &os = Catch::cout();
+        os << (failed ? "[  FAILED  ] " : "[       OK ] ") << stats.testInfo.name << '\n';
+        os.flush();
+    }
+
+    void noMatchingTestCases(std::string const &spec) override {
+        auto &os = Catch::cout();
+        os << "[  NO TEST ] no matching test cases for spec: " << spec << '\n';
+        os.flush();
+    }
+
+    void reportInvalidArguments(std::string const &arg) override {
+        auto &os = Catch::cout();
+        os << "[   ERROR  ] invalid Catch2 arguments: " << arg << '\n';
+        os.flush();
+    }
+
+    void assertionStarting(Catch::AssertionInfo const &) override {}
+
+    bool assertionEnded(Catch::AssertionStats const &) override { return false; }
+
+    void testRunEnded(Catch::TestRunStats const &stats) override {
+        auto &os = Catch::cout();
+        auto passed = stats.totals.testCases.passed;
+        auto failed = stats.totals.testCases.failed;
+        auto total = passed + failed;
+        auto assertions = stats.totals.assertions.passed + stats.totals.assertions.failed;
+        if (failed == 0) {
+            os << "[  PASSED  ] " << total << " test cases, " << assertions << " assertions.\n";
+        } else {
+            os << "[  FAILED  ] " << failed << " of " << total << " test cases, " << assertions
+               << " assertions.\n";
+        }
+        os.flush();
+    }
+
+private:
+    void print_python_version_once() {
+        if (printed_) {
+            return;
+        }
+        printed_ = true;
+        auto &os = Catch::cout();
+        os << "[ PYTHON   ] " << Py_GetVersion() << '\n';
+        os.flush();
+    }
+
+    bool printed_ = false;
+};
+
+} // namespace
+
+CATCH_REGISTER_REPORTER("progress", ProgressReporter)
+
+namespace {
+
+std::string get_utc_timestamp() {
+    auto now = std::chrono::system_clock::now();
+    auto time_t_now = std::chrono::system_clock::to_time_t(now);
+    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
+
+    std::tm utc_tm{};
+#if defined(_WIN32)
+    gmtime_s(&utc_tm, &time_t_now);
+#else
+    gmtime_r(&time_t_now, &utc_tm);
+#endif
+
+    std::ostringstream oss;
+    oss << std::put_time(&utc_tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3)
+        << ms.count() << 'Z';
+    return oss.str();
+}
+
+#ifndef _WIN32
+// Signal handler to print a message when the process is terminated.
+// Uses only async-signal-safe functions.
+void termination_signal_handler(int sig) {
+    const char *msg = "[  SIGNAL  ] Process received SIGTERM\n";
+    // write() is async-signal-safe, unlike std::cout
+    ssize_t written = write(STDOUT_FILENO, msg, strlen(msg));
+    (void) written; // suppress "unused variable" warnings
+    // Re-raise with default handler to get proper exit status
+    std::signal(sig, SIG_DFL);
+    std::raise(sig);
+}
+#endif
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+#ifndef _WIN32
+    std::signal(SIGTERM, termination_signal_handler);
+#endif
+
+    // Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number:
+    std::string updated_pythonpath("pybind11_test_with_catch_PYTHONPATH_2099743835476552");
+    const char *preexisting_pythonpath = getenv("PYTHONPATH");
+    if (preexisting_pythonpath != nullptr) {
+#if defined(_WIN32)
+        updated_pythonpath += ';';
+#else
+        updated_pythonpath += ':';
+#endif
+        updated_pythonpath += preexisting_pythonpath;
+    }
+#if defined(_WIN32)
+    _putenv_s("PYTHONPATH", updated_pythonpath.c_str());
+#else
+    setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1);
+#endif
+
+    std::cout << "[ STARTING ] " << get_utc_timestamp() << '\n';
+    std::cout.flush();
+
+    py::scoped_interpreter guard{};
+
+    auto result = Catch::Session().run(argc, argv);
+
+    std::cout << "[ DONE     ] " << get_utc_timestamp() << " (result " << result << ")\n";
+    std::cout.flush();
+
+    return result < 0xff ? result : 0xff;
+}
diff -Nru pybind11-3.0.1/tests/test_with_catch/catch_skip.h pybind11-3.0.4/tests/test_with_catch/catch_skip.h
--- pybind11-3.0.1/tests/test_with_catch/catch_skip.h	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/catch_skip.h	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,21 @@
+// Macro to skip a test at runtime with a visible message.
+// Catch2 v2 doesn't have native skip support (v3 does with SKIP()).
+// The test will count as "passed" in totals, but the output clearly shows it was skipped.
+
+#pragma once
+
+#include <pybind11/detail/pybind11_namespace_macros.h>
+
+#include <catch.hpp>
+
+#define PYBIND11_CATCH2_SKIP_IF(condition, reason)                                                \
+    do {                                                                                          \
+        PYBIND11_WARNING_PUSH                                                                     \
+        PYBIND11_WARNING_DISABLE_MSVC(4127)                                                       \
+        if (condition) {                                                                          \
+            Catch::cout() << "[ SKIPPED  ] " << (reason) << '\n';                                 \
+            Catch::cout().flush();                                                                \
+            return;                                                                               \
+        }                                                                                         \
+        PYBIND11_WARNING_POP                                                                      \
+    } while (0)
diff -Nru pybind11-3.0.1/tests/test_with_catch/CMakeLists.txt pybind11-3.0.4/tests/test_with_catch/CMakeLists.txt
--- pybind11-3.0.1/tests/test_with_catch/CMakeLists.txt	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/CMakeLists.txt	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,64 @@
+possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
+
+if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy"
+   OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
+   OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")
+  message(STATUS "Skipping embed test on PyPy or GraalPy")
+  add_custom_target(cpptest) # Dummy target on PyPy or GraalPy. Embedding is not supported.
+  set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
+  return()
+endif()
+
+if(TARGET Python::Module AND NOT TARGET Python::Python)
+  message(STATUS "Skipping embed test since no embed libs found")
+  add_custom_target(cpptest) # Dummy target since embedding is not supported.
+  set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
+  return()
+endif()
+
+find_package(Catch 2.13.10)
+
+if(CATCH_FOUND)
+  message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}")
+else()
+  message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
+                 " manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
+  return()
+endif()
+
+find_package(Threads REQUIRED)
+
+if(PYBIND11_TEST_SMART_HOLDER)
+  add_compile_definitions(
+    -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
+endif()
+
+add_executable(test_with_catch catch.cpp test_args_convert_vector.cpp test_argument_vector.cpp
+                               test_interpreter.cpp test_subinterpreter.cpp)
+pybind11_enable_warnings(test_with_catch)
+
+target_link_libraries(test_with_catch PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads)
+
+if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
+  file(COPY test_interpreter.py test_trampoline.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
+
+add_custom_target(
+  cpptest
+  COMMAND "$<TARGET_FILE:test_with_catch>"
+  DEPENDS test_with_catch
+  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+  USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
+)
+
+pybind11_add_module(external_module THIN_LTO external_module.cpp)
+set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY
+                                                 "${CMAKE_CURRENT_BINARY_DIR}")
+foreach(config ${CMAKE_CONFIGURATION_TYPES})
+  string(TOUPPER ${config} config)
+  set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
+                                                   "${CMAKE_CURRENT_BINARY_DIR}")
+endforeach()
+add_dependencies(cpptest external_module)
+
+add_dependencies(check cpptest)
diff -Nru pybind11-3.0.1/tests/test_with_catch/external_module.cpp pybind11-3.0.4/tests/test_with_catch/external_module.cpp
--- pybind11-3.0.1/tests/test_with_catch/external_module.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/external_module.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,39 @@
+#include <pybind11/pybind11.h>
+
+namespace py = pybind11;
+
+/* Simple test module/test class to check that the referenced internals data of external pybind11
+ * modules aren't preserved over a finalize/initialize.
+ */
+
+namespace {
+// Compare unsafe_reset_internals_for_single_interpreter in
+// test_subinterpreter.cpp.
+void unsafe_reset_local_internals() {
+    // NOTE: This code is NOT SAFE unless the caller guarantees no other threads are alive
+    // NOTE: This code is tied to the precise implementation of the internals holder
+
+    py::detail::get_local_internals_pp_manager().unref();
+    py::detail::get_local_internals();
+}
+} // namespace
+
+PYBIND11_MODULE(external_module,
+                m,
+                py::mod_gil_not_used(),
+                py::multiple_interpreters::per_interpreter_gil()) {
+    // At least one test ("Single Subinterpreter") wants to reset
+    // internals. We have separate local internals because we are a
+    // separate DSO, so ours need to be reset too!
+    unsafe_reset_local_internals();
+    class A {
+    public:
+        explicit A(int value) : v{value} {};
+        int v;
+    };
+
+    py::class_<A>(m, "A").def(py::init<int>()).def_readwrite("value", &A::v);
+
+    m.def("internals_at",
+          []() { return reinterpret_cast<uintptr_t>(&py::detail::get_internals()); });
+}
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_args_convert_vector.cpp pybind11-3.0.4/tests/test_with_catch/test_args_convert_vector.cpp
--- pybind11-3.0.1/tests/test_with_catch/test_args_convert_vector.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_args_convert_vector.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,80 @@
+#include "pybind11/pybind11.h"
+#include "catch.hpp"
+
+namespace py = pybind11;
+
+using args_convert_vector = py::detail::args_convert_vector<py::detail::arg_vector_small_size>;
+
+namespace {
+template <typename Container>
+std::vector<Container> get_sample_vectors() {
+    std::vector<Container> result;
+    result.emplace_back();
+    for (const auto sz : {0, 4, 5, 6, 31, 32, 33, 63, 64, 65}) {
+        for (const bool b : {false, true}) {
+            result.emplace_back(static_cast<std::size_t>(sz), b);
+        }
+    }
+    return result;
+}
+
+void require_vector_matches_sample(const args_convert_vector &actual,
+                                   const std::vector<bool> &expected) {
+    REQUIRE(actual.size() == expected.size());
+    for (size_t ii = 0; ii < actual.size(); ++ii) {
+        REQUIRE(actual[ii] == expected[ii]);
+    }
+}
+
+template <typename ActualMutationFunc, typename ExpectedMutationFunc>
+void mutation_test_with_samples(ActualMutationFunc actual_mutation_func,
+                                ExpectedMutationFunc expected_mutation_func) {
+    auto sample_contents = get_sample_vectors<std::vector<bool>>();
+    auto samples = get_sample_vectors<args_convert_vector>();
+    for (size_t ii = 0; ii < samples.size(); ++ii) {
+        auto &actual = samples[ii];
+        auto &expected = sample_contents[ii];
+
+        actual_mutation_func(actual);
+        expected_mutation_func(expected);
+        require_vector_matches_sample(actual, expected);
+    }
+}
+} // namespace
+
+// I would like to write [capture](auto& vec) block inline, but we
+// have to work with C++11, which doesn't have generic lambdas.
+// NOLINTBEGIN(bugprone-macro-parentheses)
+#define MUTATION_LAMBDA(capture, block)                                                           \
+    [capture](args_convert_vector & vec) block, [capture](std::vector<bool> & vec) block
+// NOLINTEND(bugprone-macro-parentheses)
+
+// For readability, rather than having ugly empty arguments.
+#define NO_CAPTURE
+
+TEST_CASE("check sample args_convert_vector contents") {
+    mutation_test_with_samples(MUTATION_LAMBDA(NO_CAPTURE, { (void) vec; }));
+}
+
+TEST_CASE("args_convert_vector push_back") {
+    for (const bool b : {false, true}) {
+        mutation_test_with_samples(MUTATION_LAMBDA(b, { vec.push_back(b); }));
+    }
+}
+
+TEST_CASE("args_convert_vector reserve") {
+    for (std::size_t ii = 0; ii < 4; ++ii) {
+        mutation_test_with_samples(MUTATION_LAMBDA(ii, { vec.reserve(ii); }));
+    }
+}
+
+TEST_CASE("args_convert_vector reserve then push_back") {
+    for (std::size_t ii = 0; ii < 4; ++ii) {
+        for (const bool b : {false, true}) {
+            mutation_test_with_samples(MUTATION_LAMBDA(=, {
+                vec.reserve(ii);
+                vec.push_back(b);
+            }));
+        }
+    }
+}
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_argument_vector.cpp pybind11-3.0.4/tests/test_with_catch/test_argument_vector.cpp
--- pybind11-3.0.1/tests/test_with_catch/test_argument_vector.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_argument_vector.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,94 @@
+#include "pybind11/pybind11.h"
+#include "catch.hpp"
+
+namespace py = pybind11;
+
+// 2 is chosen because it is the smallest number (keeping tests short)
+// where we can create non-empty vectors whose size is the inline size
+// plus or minus 1.
+using argument_vector = py::detail::argument_vector<2>;
+
+namespace {
+argument_vector to_argument_vector(const std::vector<py::handle> &v) {
+    argument_vector result;
+    result.reserve(v.size());
+    for (const auto x : v) {
+        result.push_back(x);
+    }
+    return result;
+}
+
+std::vector<std::vector<py::handle>> get_sample_argument_vector_contents() {
+    return std::vector<std::vector<py::handle>>{
+        {},
+        {py::handle(Py_None)},
+        {py::handle(Py_None), py::handle(Py_False)},
+        {py::handle(Py_None), py::handle(Py_False), py::handle(Py_True)},
+    };
+}
+
+std::vector<argument_vector> get_sample_argument_vectors() {
+    std::vector<argument_vector> result;
+    for (const auto &vec : get_sample_argument_vector_contents()) {
+        result.push_back(to_argument_vector(vec));
+    }
+    return result;
+}
+
+void require_vector_matches_sample(const argument_vector &actual,
+                                   const std::vector<py::handle> &expected) {
+    REQUIRE(actual.size() == expected.size());
+    for (size_t ii = 0; ii < actual.size(); ++ii) {
+        REQUIRE(actual[ii].ptr() == expected[ii].ptr());
+    }
+}
+
+template <typename ActualMutationFunc, typename ExpectedMutationFunc>
+void mutation_test_with_samples(ActualMutationFunc actual_mutation_func,
+                                ExpectedMutationFunc expected_mutation_func) {
+    auto sample_contents = get_sample_argument_vector_contents();
+    auto samples = get_sample_argument_vectors();
+    for (size_t ii = 0; ii < samples.size(); ++ii) {
+        auto &actual = samples[ii];
+        auto &expected = sample_contents[ii];
+
+        actual_mutation_func(actual);
+        expected_mutation_func(expected);
+        require_vector_matches_sample(actual, expected);
+    }
+}
+
+} // namespace
+
+// I would like to write [capture](auto& vec) block inline, but we
+// have to work with C++11, which doesn't have generic lambdas.
+// NOLINTBEGIN(bugprone-macro-parentheses)
+#define MUTATION_LAMBDA(capture, block)                                                           \
+    [capture](argument_vector & vec) block, [capture](std::vector<py::handle> & vec) block
+// NOLINTEND(bugprone-macro-parentheses)
+
+// For readability, rather than having ugly empty arguments.
+#define NO_CAPTURE
+
+TEST_CASE("check sample argument_vector contents") {
+    mutation_test_with_samples(MUTATION_LAMBDA(NO_CAPTURE, { (void) vec; }));
+}
+
+TEST_CASE("argument_vector push_back") {
+    mutation_test_with_samples(MUTATION_LAMBDA(NO_CAPTURE, { vec.emplace_back(Py_None); }));
+}
+
+TEST_CASE("argument_vector reserve") {
+    for (std::size_t ii = 0; ii < 4; ++ii) {
+        mutation_test_with_samples(MUTATION_LAMBDA(ii, { vec.reserve(ii); }));
+    }
+}
+
+TEST_CASE("argument_vector reserve then push_back") {
+    for (std::size_t ii = 0; ii < 4; ++ii) {
+        mutation_test_with_samples(MUTATION_LAMBDA(ii, {
+            vec.reserve(ii);
+            vec.emplace_back(Py_True);
+        }));
+    }
+}
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_interpreter.cpp pybind11-3.0.4/tests/test_with_catch/test_interpreter.cpp
--- pybind11-3.0.1/tests/test_with_catch/test_interpreter.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_interpreter.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,511 @@
+#include <pybind11/critical_section.h>
+#include <pybind11/embed.h>
+
+// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
+// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
+PYBIND11_WARNING_DISABLE_MSVC(4996)
+
+#include "catch_skip.h"
+
+#include <catch.hpp>
+#include <cstdlib>
+#include <fstream>
+#include <functional>
+#include <thread>
+#include <utility>
+
+namespace py = pybind11;
+using namespace py::literals;
+
+size_t get_sys_path_size() {
+    auto sys_path = py::module::import("sys").attr("path");
+    return py::len(sys_path);
+}
+
+bool has_state_dict_internals_obj() {
+    py::dict state = py::detail::get_python_state_dict();
+    return state.contains(PYBIND11_INTERNALS_ID);
+}
+
+uintptr_t get_details_as_uintptr() {
+    return reinterpret_cast<uintptr_t>(py::detail::get_internals_pp_manager().get_pp()->get());
+}
+
+class Widget {
+public:
+    explicit Widget(std::string message) : message(std::move(message)) {}
+    virtual ~Widget() = default;
+
+    std::string the_message() const { return message; }
+    virtual int the_answer() const = 0;
+    virtual std::string argv0() const = 0;
+
+private:
+    std::string message;
+};
+
+class PyWidget final : public Widget {
+    using Widget::Widget;
+
+    int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); }
+    std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); }
+};
+
+class test_override_cache_helper {
+
+public:
+    virtual int func() { return 0; }
+
+    test_override_cache_helper() = default;
+    virtual ~test_override_cache_helper() = default;
+    // Non-copyable
+    test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete;
+    test_override_cache_helper(test_override_cache_helper const &Copy) = delete;
+};
+
+class test_override_cache_helper_trampoline : public test_override_cache_helper {
+    int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
+};
+
+PYBIND11_EMBEDDED_MODULE(widget_module, m, py::multiple_interpreters::per_interpreter_gil()) {
+    py::class_<Widget, PyWidget>(m, "Widget")
+        .def(py::init<std::string>())
+        .def_property_readonly("the_message", &Widget::the_message);
+
+    m.def("add", [](int i, int j) { return i + j; });
+
+    auto sub = m.def_submodule("sub");
+    sub.def("add", [](int i, int j) { return i + j; });
+}
+
+PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
+    py::class_<test_override_cache_helper,
+               test_override_cache_helper_trampoline,
+               std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper")
+        .def(py::init_alias<>())
+        .def("func", &test_override_cache_helper::func);
+}
+
+enum class SomeEnum { value1, value2 }; // Added in PR #6015
+
+PYBIND11_EMBEDDED_MODULE(enum_module, m, py::multiple_interpreters::per_interpreter_gil()) {
+    py::enum_<SomeEnum>(m, "SomeEnum")
+        .value("value1", SomeEnum::value1)
+        .value("value2", SomeEnum::value2);
+}
+
+PYBIND11_EMBEDDED_MODULE(throw_exception, ) { throw std::runtime_error("C++ Error"); }
+
+PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
+    auto d = py::dict();
+    d["missing"].cast<py::object>();
+}
+
+TEST_CASE("PYTHONPATH is used to update sys.path") {
+    // The setup for this TEST_CASE is in catch.cpp!
+    auto sys_path = py::str(py::module_::import("sys").attr("path")).cast<std::string>();
+    REQUIRE_THAT(
+        sys_path,
+        Catch::Matchers::Contains("pybind11_test_with_catch_PYTHONPATH_2099743835476552"));
+}
+
+TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
+    auto module_ = py::module_::import("test_interpreter");
+    REQUIRE(py::hasattr(module_, "DerivedWidget"));
+
+    auto locals = py::dict("hello"_a = "Hello, World!", "x"_a = 5, **module_.attr("__dict__"));
+    py::exec(R"(
+        widget = DerivedWidget("{} - {}".format(hello, x))
+        message = widget.the_message
+    )",
+             py::globals(),
+             locals);
+    REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5");
+
+    auto py_widget = module_.attr("DerivedWidget")("The question");
+    auto message = py_widget.attr("the_message");
+    REQUIRE(message.cast<std::string>() == "The question");
+
+    const auto &cpp_widget = py_widget.cast<const Widget &>();
+    REQUIRE(cpp_widget.the_answer() == 42);
+}
+
+TEST_CASE("Override cache") {
+    auto module_ = py::module_::import("test_trampoline");
+    REQUIRE(py::hasattr(module_, "func"));
+    REQUIRE(py::hasattr(module_, "func2"));
+
+    auto locals = py::dict(**module_.attr("__dict__"));
+
+    int i = 0;
+    for (; i < 1500; ++i) {
+        std::shared_ptr<test_override_cache_helper> p_obj;
+        std::shared_ptr<test_override_cache_helper> p_obj2;
+
+        py::object loc_inst = locals["func"]();
+        p_obj = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
+
+        int ret = p_obj->func();
+
+        REQUIRE(ret == 42);
+
+        loc_inst = locals["func2"]();
+
+        p_obj2 = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
+
+        p_obj2->func();
+    }
+}
+
+TEST_CASE("Import error handling") {
+    REQUIRE_NOTHROW(py::module_::import("widget_module"));
+    REQUIRE_THROWS_WITH(py::module_::import("throw_exception"), "ImportError: C++ Error");
+    REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
+                        Catch::Contains("ImportError: initialization failed"));
+
+    auto locals = py::dict("is_keyerror"_a = false, "message"_a = "not set");
+    py::exec(R"(
+        try:
+            import throw_error_already_set
+        except ImportError as e:
+            is_keyerror = type(e.__cause__) == KeyError
+            message = str(e.__cause__)
+    )",
+             py::globals(),
+             locals);
+    REQUIRE(locals["is_keyerror"].cast<bool>() == true);
+    REQUIRE(locals["message"].cast<std::string>() == "'missing'");
+}
+
+TEST_CASE("There can be only one interpreter") {
+    static_assert(std::is_move_constructible<py::scoped_interpreter>::value, "");
+    static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, "");
+    static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, "");
+    static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, "");
+
+    REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running");
+    REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running");
+
+    py::finalize_interpreter();
+    REQUIRE_NOTHROW(py::scoped_interpreter());
+    {
+        auto pyi1 = py::scoped_interpreter();
+        auto pyi2 = std::move(pyi1);
+    }
+    py::initialize_interpreter();
+}
+
+#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
+TEST_CASE("Custom PyConfig") {
+    py::finalize_interpreter();
+    PyConfig config;
+    PyConfig_InitPythonConfig(&config);
+    REQUIRE_NOTHROW(py::scoped_interpreter{&config});
+    {
+        py::scoped_interpreter p{&config};
+        REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast<int>() == 42);
+    }
+    py::initialize_interpreter();
+}
+
+TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") {
+    std::vector<std::string> path;
+    for (auto p : py::module::import("sys").attr("path")) {
+        path.emplace_back(py::str(p));
+    }
+
+    py::finalize_interpreter();
+    {
+        PyConfig config;
+        PyConfig_InitIsolatedConfig(&config);
+        char *argv[] = {strdup("a.out")};
+        py::scoped_interpreter argv_scope{&config, 1, argv, true};
+        std::free(argv[0]);
+        // Because this config is isolated, setting the path during init will not work, we have to
+        // set it manually.  If we don't set it, then we can't import "test_interpreter"
+        for (auto &&p : path) {
+            py::list(py::module::import("sys").attr("path")).append(p);
+        }
+        try {
+            auto module = py::module::import("test_interpreter");
+            auto py_widget = module.attr("DerivedWidget")("The question");
+            const auto &cpp_widget = py_widget.cast<const Widget &>();
+            REQUIRE(cpp_widget.argv0() == "a.out");
+        } catch (py::error_already_set &e) {
+            // catch here so that the exception doesn't escape the interpreter that owns it
+            FAIL(e.what());
+        }
+    }
+    py::initialize_interpreter();
+}
+
+TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") {
+    py::finalize_interpreter();
+    {
+        PyConfig config;
+        PyConfig_InitPythonConfig(&config);
+
+        // `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by
+        // changing it to `0`. This test exercises `scoped_interpreter` with the default config.
+        char *argv[] = {strdup("a.out"), strdup("arg1")};
+        py::scoped_interpreter argv_scope(&config, 2, argv);
+        std::free(argv[0]);
+        std::free(argv[1]);
+        auto module = py::module::import("test_interpreter");
+        auto py_widget = module.attr("DerivedWidget")("The question");
+        const auto &cpp_widget = py_widget.cast<const Widget &>();
+        REQUIRE(cpp_widget.argv0() == "arg1");
+    }
+    py::initialize_interpreter();
+}
+#endif
+
+TEST_CASE("Add program dir to path pre-PyConfig") {
+    py::finalize_interpreter();
+    size_t path_size_add_program_dir_to_path_false = 0;
+    {
+        py::scoped_interpreter scoped_interp{true, 0, nullptr, false};
+        path_size_add_program_dir_to_path_false = get_sys_path_size();
+    }
+    {
+        py::scoped_interpreter scoped_interp{};
+        REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
+    }
+    py::initialize_interpreter();
+}
+
+#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
+TEST_CASE("Add program dir to path using PyConfig") {
+    py::finalize_interpreter();
+    size_t path_size_add_program_dir_to_path_false = 0;
+    {
+        PyConfig config;
+        PyConfig_InitPythonConfig(&config);
+        py::scoped_interpreter scoped_interp{&config, 0, nullptr, false};
+        path_size_add_program_dir_to_path_false = get_sys_path_size();
+    }
+    {
+        PyConfig config;
+        PyConfig_InitPythonConfig(&config);
+        py::scoped_interpreter scoped_interp{&config};
+        REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
+    }
+    py::initialize_interpreter();
+}
+#endif
+
+TEST_CASE("Restart the interpreter") {
+    // Verify pre-restart state.
+    REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
+    REQUIRE(has_state_dict_internals_obj());
+    REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
+            == 123);
+
+    // local and foreign module internals should point to the same internals:
+    REQUIRE(get_details_as_uintptr()
+            == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
+
+    // Restart the interpreter.
+    py::finalize_interpreter();
+    REQUIRE(Py_IsInitialized() == 0);
+
+    py::initialize_interpreter();
+    REQUIRE(Py_IsInitialized() == 1);
+
+    // Internals are deleted after a restart.
+    REQUIRE_FALSE(has_state_dict_internals_obj());
+    REQUIRE(get_details_as_uintptr() == 0);
+    pybind11::detail::get_internals();
+    REQUIRE(has_state_dict_internals_obj());
+    REQUIRE(get_details_as_uintptr() != 0);
+    REQUIRE(get_details_as_uintptr()
+            == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
+
+    // Make sure that an interpreter with no get_internals() created until finalize still gets the
+    // internals destroyed
+    py::finalize_interpreter();
+    py::initialize_interpreter();
+    bool ran = false;
+    py::module_::import("__main__").attr("internals_destroy_test")
+        = py::capsule(&ran, [](void *ran) {
+              py::detail::get_internals();
+              REQUIRE(has_state_dict_internals_obj());
+              *static_cast<bool *>(ran) = true;
+          });
+    REQUIRE_FALSE(has_state_dict_internals_obj());
+    REQUIRE_FALSE(ran);
+    py::finalize_interpreter();
+    REQUIRE(ran);
+    py::initialize_interpreter();
+    REQUIRE_FALSE(has_state_dict_internals_obj());
+    REQUIRE(get_details_as_uintptr() == 0);
+
+    // C++ modules can be reloaded.
+    auto cpp_module = py::module_::import("widget_module");
+    REQUIRE(cpp_module.attr("add")(1, 2).cast<int>() == 3);
+
+    // Also verify submodules work
+    REQUIRE(cpp_module.attr("sub").attr("add")(1, 41).cast<int>() == 42);
+
+    // C++ type information is reloaded and can be used in python modules.
+    auto py_module = py::module_::import("test_interpreter");
+    auto py_widget = py_module.attr("DerivedWidget")("Hello after restart");
+    REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
+}
+
+TEST_CASE("Enum module survives restart") { // Added in PR #6015
+    // Regression test for gh-5976: py::enum_ uses def_property_static, which
+    // calls process_attributes::init after initialize_generic's strdup loop,
+    // leaving arg names as string literals. Without the fix, destruct() would
+    // call free() on those literals during interpreter finalization.
+    PYBIND11_CATCH2_SKIP_IF(PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 12,
+                            "Pre-existing crash in enum cleanup during finalize on Python 3.12");
+
+    auto enum_mod = py::module_::import("enum_module");
+    REQUIRE(enum_mod.attr("SomeEnum").attr("value1").attr("name").cast<std::string>() == "value1");
+
+    py::finalize_interpreter();
+    py::initialize_interpreter();
+
+    enum_mod = py::module_::import("enum_module");
+    REQUIRE(enum_mod.attr("SomeEnum").attr("value2").attr("name").cast<std::string>() == "value2");
+}
+
+TEST_CASE("Execution frame") {
+    // When the interpreter is embedded, there is no execution frame, but `py::exec`
+    // should still function by using reasonable globals: `__main__.__dict__`.
+    py::exec("var = dict(number=42)");
+    REQUIRE(py::globals()["var"]["number"].cast<int>() == 42);
+}
+
+TEST_CASE("Threads") {
+    // Restart interpreter to ensure threads are not initialized
+    py::finalize_interpreter();
+    py::initialize_interpreter();
+
+    constexpr auto num_threads = 10;
+    auto locals = py::dict("count"_a = 0);
+
+    {
+        py::gil_scoped_release gil_release{};
+#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX < 0x030E0000
+        std::mutex mutex;
+#endif
+
+        auto threads = std::vector<std::thread>();
+        for (auto i = 0; i < num_threads; ++i) {
+            threads.emplace_back([&]() {
+                py::gil_scoped_acquire gil{};
+#ifdef Py_GIL_DISABLED
+#    if PY_VERSION_HEX < 0x030E0000
+                // This will not run with the GIL, so it won't deadlock. That's
+                // because of how we run our tests. Be more careful of
+                // deadlocks if the "free-threaded" GIL could be enabled (at
+                // runtime).
+                std::lock_guard<std::mutex> lock(mutex);
+#    else
+                // CPython's thread-safe API in no-GIL mode.
+                py::scoped_critical_section lock(locals);
+#    endif
+#endif
+                locals["count"] = locals["count"].cast<int>() + 1;
+            });
+        }
+
+        for (auto &thread : threads) {
+            thread.join();
+        }
+    }
+
+    REQUIRE(locals["count"].cast<int>() == num_threads);
+}
+
+// Scope exit utility https://stackoverflow.com/a/36644501/7255855
+struct scope_exit {
+    std::function<void()> f_;
+    explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
+    ~scope_exit() {
+        if (f_) {
+            f_();
+        }
+    }
+};
+
+TEST_CASE("Reload module from file") {
+    // Disable generation of cached bytecode (.pyc files) for this test, otherwise
+    // Python might pick up an old version from the cache instead of the new versions
+    // of the .py files generated below
+    auto sys = py::module_::import("sys");
+    bool dont_write_bytecode = sys.attr("dont_write_bytecode").cast<bool>();
+    sys.attr("dont_write_bytecode") = true;
+    // Reset the value at scope exit
+    scope_exit reset_dont_write_bytecode(
+        [&]() { sys.attr("dont_write_bytecode") = dont_write_bytecode; });
+
+    std::string module_name = "test_module_reload";
+    std::string module_file = module_name + ".py";
+
+    // Create the module .py file
+    std::ofstream test_module(module_file);
+    test_module << "def test():\n";
+    test_module << "    return 1\n";
+    test_module.close();
+    // Delete the file at scope exit
+    scope_exit delete_module_file([&]() { std::remove(module_file.c_str()); });
+
+    // Import the module from file
+    auto module_ = py::module_::import(module_name.c_str());
+    int result = module_.attr("test")().cast<int>();
+    REQUIRE(result == 1);
+
+    // Update the module .py file with a small change
+    test_module.open(module_file);
+    test_module << "def test():\n";
+    test_module << "    return 2\n";
+    test_module.close();
+
+    // Reload the module
+    module_.reload();
+    result = module_.attr("test")().cast<int>();
+    REQUIRE(result == 2);
+}
+
+TEST_CASE("sys.argv gets initialized properly") {
+    py::finalize_interpreter();
+    {
+        py::scoped_interpreter default_scope;
+        auto module = py::module::import("test_interpreter");
+        auto py_widget = module.attr("DerivedWidget")("The question");
+        const auto &cpp_widget = py_widget.cast<const Widget &>();
+        REQUIRE(cpp_widget.argv0().empty());
+    }
+
+    {
+        char *argv[] = {strdup("a.out")};
+        py::scoped_interpreter argv_scope(true, 1, argv);
+        std::free(argv[0]);
+        auto module = py::module::import("test_interpreter");
+        auto py_widget = module.attr("DerivedWidget")("The question");
+        const auto &cpp_widget = py_widget.cast<const Widget &>();
+        REQUIRE(cpp_widget.argv0() == "a.out");
+    }
+    py::initialize_interpreter();
+}
+
+TEST_CASE("make_iterator can be called before then after finalizing an interpreter") {
+    // Reproduction of issue #2101 (https://github.com/pybind/pybind11/issues/2101)
+    py::finalize_interpreter();
+
+    std::vector<int> container;
+    {
+        pybind11::scoped_interpreter g;
+        auto iter = pybind11::make_iterator(container.begin(), container.end());
+    }
+
+    REQUIRE_NOTHROW([&]() {
+        pybind11::scoped_interpreter g;
+        auto iter = pybind11::make_iterator(container.begin(), container.end());
+    }());
+
+    py::initialize_interpreter();
+}
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_interpreter.py pybind11-3.0.4/tests/test_with_catch/test_interpreter.py
--- pybind11-3.0.1/tests/test_with_catch/test_interpreter.py	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_interpreter.py	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+import sys
+
+from widget_module import Widget
+
+
+class DerivedWidget(Widget):
+    def __init__(self, message):
+        super().__init__(message)
+
+    def the_answer(self):
+        return 42
+
+    def argv0(self):
+        return sys.argv[0]
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_subinterpreter.cpp pybind11-3.0.4/tests/test_with_catch/test_subinterpreter.cpp
--- pybind11-3.0.1/tests/test_with_catch/test_subinterpreter.cpp	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_subinterpreter.cpp	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,579 @@
+#include <pybind11/embed.h>
+#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
+#    include <pybind11/gil_safe_call_once.h>
+#    include <pybind11/subinterpreter.h>
+
+// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
+// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
+PYBIND11_WARNING_DISABLE_MSVC(4996)
+
+#    include "catch_skip.h"
+
+#    include <catch.hpp>
+#    include <cstdlib>
+#    include <fstream>
+#    include <functional>
+#    include <thread>
+#    include <utility>
+
+namespace py = pybind11;
+using namespace py::literals;
+
+bool has_state_dict_internals_obj();
+uintptr_t get_details_as_uintptr();
+
+void unsafe_reset_internals_for_single_interpreter() {
+    // NOTE: This code is NOT SAFE unless the caller guarantees no other threads are alive
+    // NOTE: This code is tied to the precise implementation of the internals holder
+
+    // first, unref the thread local internals
+    py::detail::get_internals_pp_manager().unref();
+    py::detail::get_local_internals_pp_manager().unref();
+
+    // we know there are no other interpreters, so we can lower this. SUPER DANGEROUS
+    py::detail::has_seen_non_main_interpreter() = false;
+
+    // now we unref the static global singleton internals
+    py::detail::get_internals_pp_manager().unref();
+    py::detail::get_local_internals_pp_manager().unref();
+
+    // finally, we reload the static global singleton
+    py::detail::get_internals();
+    py::detail::get_local_internals();
+}
+
+py::object &get_dict_type_object() {
+    PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
+    return storage
+        .call_once_and_store_result(
+            []() -> py::object { return py::module_::import("builtins").attr("dict"); })
+        .get_stored();
+}
+
+py::object &get_ordered_dict_type_object() {
+    PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
+    return storage
+        .call_once_and_store_result(
+            []() -> py::object { return py::module_::import("collections").attr("OrderedDict"); })
+        .get_stored();
+}
+
+py::object &get_default_dict_type_object() {
+    PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
+    return storage
+        .call_once_and_store_result(
+            []() -> py::object { return py::module_::import("collections").attr("defaultdict"); })
+        .get_stored();
+}
+
+TEST_CASE("Single Subinterpreter") {
+    unsafe_reset_internals_for_single_interpreter();
+
+    py::module_::import("external_module"); // in the main interpreter
+
+    // Add tags to the modules in the main interpreter and test the basics.
+    py::module_::import("__main__").attr("main_tag") = "main interpreter";
+    {
+        auto m = py::module_::import("widget_module");
+        m.attr("extension_module_tag") = "added to module in main interpreter";
+
+        REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
+    }
+    REQUIRE(has_state_dict_internals_obj());
+
+    auto main_int
+        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+
+    /// Create and switch to a subinterpreter.
+    {
+        py::scoped_subinterpreter ssi;
+
+        // The subinterpreter has internals populated
+        REQUIRE(has_state_dict_internals_obj());
+
+        py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+
+        auto ext_int
+            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+        py::detail::get_internals();
+        REQUIRE(get_details_as_uintptr() == ext_int);
+        REQUIRE(ext_int != main_int);
+
+        // Modules tags should be gone.
+        REQUIRE_FALSE(py::hasattr(py::module_::import("__main__"), "tag"));
+        {
+            auto m = py::module_::import("widget_module");
+            REQUIRE_FALSE(py::hasattr(m, "extension_module_tag"));
+
+            // Function bindings should still work.
+            REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
+        }
+    }
+
+    REQUIRE(py::hasattr(py::module_::import("__main__"), "main_tag"));
+    REQUIRE(py::hasattr(py::module_::import("widget_module"), "extension_module_tag"));
+    REQUIRE(has_state_dict_internals_obj());
+
+    unsafe_reset_internals_for_single_interpreter();
+}
+
+#    if PY_VERSION_HEX >= 0x030D0000
+TEST_CASE("Move Subinterpreter") {
+    std::unique_ptr<py::subinterpreter> sub(new py::subinterpreter(py::subinterpreter::create()));
+
+    // on this thread, use the subinterpreter and import some non-trivial junk
+    {
+        py::subinterpreter_scoped_activate activate(*sub);
+
+        py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+        py::module_::import("datetime");
+        py::module_::import("threading");
+        py::module_::import("external_module");
+    }
+
+    auto t = std::thread([&]() {
+        // Use it again
+        {
+            py::subinterpreter_scoped_activate activate(*sub);
+            py::module_::import("external_module");
+        }
+        sub.reset();
+    });
+
+    // on 3.14.1+ destructing a sub-interpreter does a stop-the-world.  we need to detach our
+    // thread state in order for that to be possible.
+    {
+        py::gil_scoped_release nogil;
+        t.join();
+    }
+
+    REQUIRE(!sub);
+
+    unsafe_reset_internals_for_single_interpreter();
+}
+#    endif
+
+TEST_CASE("GIL Subinterpreter") {
+
+    PyInterpreterState *main_interp = PyInterpreterState_Get();
+
+    {
+        auto sub = py::subinterpreter::create();
+
+        REQUIRE(main_interp == PyInterpreterState_Get());
+
+        PyInterpreterState *sub_interp = nullptr;
+
+        {
+            py::subinterpreter_scoped_activate activate(sub);
+
+            sub_interp = PyInterpreterState_Get();
+            REQUIRE(sub_interp != main_interp);
+
+            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+            py::module_::import("datetime");
+            py::module_::import("threading");
+            py::module_::import("external_module");
+
+            {
+                py::subinterpreter_scoped_activate main(py::subinterpreter::main());
+                REQUIRE(PyInterpreterState_Get() == main_interp);
+
+                {
+                    py::gil_scoped_release nogil{};
+                    {
+                        py::gil_scoped_acquire yesgil{};
+                        REQUIRE(PyInterpreterState_Get() == main_interp);
+                    }
+                }
+
+                REQUIRE(PyInterpreterState_Get() == main_interp);
+            }
+
+            REQUIRE(PyInterpreterState_Get() == sub_interp);
+
+            {
+                py::gil_scoped_release nogil{};
+                {
+                    py::gil_scoped_acquire yesgil{};
+                    REQUIRE(PyInterpreterState_Get() == sub_interp);
+                }
+            }
+
+            REQUIRE(PyInterpreterState_Get() == sub_interp);
+        }
+
+        REQUIRE(PyInterpreterState_Get() == main_interp);
+
+        {
+            py::gil_scoped_release nogil{};
+            {
+                py::gil_scoped_acquire yesgil{};
+                REQUIRE(PyInterpreterState_Get() == main_interp);
+            }
+        }
+
+        REQUIRE(PyInterpreterState_Get() == main_interp);
+
+        bool thread_result;
+
+        {
+            thread_result = false;
+            py::gil_scoped_release nogil{};
+            std::thread([&]() {
+                {
+                    py::subinterpreter_scoped_activate ssa{sub};
+                }
+                {
+                    py::gil_scoped_acquire gil{};
+                    thread_result = (PyInterpreterState_Get() == main_interp);
+                }
+            }).join();
+        }
+        REQUIRE(thread_result);
+
+        {
+            thread_result = false;
+            py::gil_scoped_release nogil{};
+            std::thread([&]() {
+                py::gil_scoped_acquire gil{};
+                thread_result = (PyInterpreterState_Get() == main_interp);
+            }).join();
+        }
+        REQUIRE(thread_result);
+    }
+
+    REQUIRE(PyInterpreterState_Get() == main_interp);
+    unsafe_reset_internals_for_single_interpreter();
+}
+
+TEST_CASE("Multiple Subinterpreters") {
+    unsafe_reset_internals_for_single_interpreter();
+
+    // Make sure the module is in the main interpreter and save its pointer
+    auto *main_ext = py::module_::import("external_module").ptr();
+    auto main_int
+        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+    py::module_::import("external_module").attr("multi_interp") = "1";
+
+    {
+        py::subinterpreter si1 = py::subinterpreter::create();
+        std::unique_ptr<py::subinterpreter> psi2;
+
+        PyObject *sub1_ext = nullptr;
+        PyObject *sub2_ext = nullptr;
+        uintptr_t sub1_int = 0;
+        uintptr_t sub2_int = 0;
+
+        {
+            py::subinterpreter_scoped_activate scoped(si1);
+            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+
+            // The subinterpreter has its own copy of this module which is completely separate from
+            // main
+            sub1_ext = py::module_::import("external_module").ptr();
+            REQUIRE(sub1_ext != main_ext);
+            REQUIRE_FALSE(py::hasattr(py::module_::import("external_module"), "multi_interp"));
+            py::module_::import("external_module").attr("multi_interp") = "2";
+            // The subinterpreter also has its own internals
+            sub1_int
+                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+            REQUIRE(sub1_int != main_int);
+
+            // while the old one is active, create a new one
+            psi2.reset(new py::subinterpreter(py::subinterpreter::create()));
+        }
+
+        {
+            py::subinterpreter_scoped_activate scoped(*psi2);
+            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+
+            // The second subinterpreter is separate from both main and the other subinterpreter
+            sub2_ext = py::module_::import("external_module").ptr();
+            REQUIRE(sub2_ext != main_ext);
+            REQUIRE(sub2_ext != sub1_ext);
+            REQUIRE_FALSE(py::hasattr(py::module_::import("external_module"), "multi_interp"));
+            py::module_::import("external_module").attr("multi_interp") = "3";
+            // The subinterpreter also has its own internals
+            sub2_int
+                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+            REQUIRE(sub2_int != main_int);
+            REQUIRE(sub2_int != sub1_int);
+        }
+
+        {
+            py::subinterpreter_scoped_activate scoped(si1);
+            REQUIRE(
+                py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
+                == "2");
+        }
+
+        // out here we should be in the main interpreter, with the GIL, with the other 2 still
+        // alive
+
+        auto post_int
+            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+        // Make sure internals went back the way it was before
+        REQUIRE(main_int == post_int);
+
+        REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
+                == "1");
+    }
+
+    // now back to just main
+
+    auto post_int
+        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+    // Make sure internals went back the way it was before
+    REQUIRE(main_int == post_int);
+
+    REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
+            == "1");
+
+    unsafe_reset_internals_for_single_interpreter();
+}
+
+// Test that gil_safe_call_once_and_store provides per-interpreter storage.
+// Without the per-interpreter storage fix, the subinterpreter would see the value
+// cached by the main interpreter, which is invalid (different interpreter's object).
+TEST_CASE("gil_safe_call_once_and_store per-interpreter isolation") {
+    unsafe_reset_internals_for_single_interpreter();
+
+    // This static simulates a typical usage pattern where a module caches
+    // an imported object using gil_safe_call_once_and_store.
+    PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
+
+    // Get the interpreter ID in the main interpreter
+    auto main_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
+
+    // Store a value in the main interpreter - we'll store the interpreter ID as a Python int
+    auto &main_value = storage
+                           .call_once_and_store_result([]() {
+                               return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
+                           })
+                           .get_stored();
+    REQUIRE(main_value.cast<int64_t>() == main_interp_id);
+
+    py::object dict_type = get_dict_type_object();
+    py::object ordered_dict_type = get_ordered_dict_type_object();
+    py::object default_dict_type = get_default_dict_type_object();
+
+    int64_t sub_interp_id = -1;
+    int64_t sub_cached_value = -1;
+
+    bool sub_default_dict_type_destroyed = false;
+
+    // Create a subinterpreter and check that it gets its own storage
+    {
+        py::scoped_subinterpreter ssi;
+
+        sub_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
+        REQUIRE(sub_interp_id != main_interp_id);
+
+        // Access the same static storage from the subinterpreter.
+        // With per-interpreter storage, this should call the lambda again
+        // and cache a NEW value for this interpreter.
+        // Without per-interpreter storage, this would return main's cached value.
+        auto &sub_value
+            = storage
+                  .call_once_and_store_result([]() {
+                      return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
+                  })
+                  .get_stored();
+
+        sub_cached_value = sub_value.cast<int64_t>();
+
+        // The cached value should be the SUBINTERPRETER's ID, not the main interpreter's.
+        // This would fail without per-interpreter storage.
+        REQUIRE(sub_cached_value == sub_interp_id);
+        REQUIRE(sub_cached_value != main_interp_id);
+
+        py::object sub_dict_type = get_dict_type_object();
+        py::object sub_ordered_dict_type = get_ordered_dict_type_object();
+        py::object sub_default_dict_type = get_default_dict_type_object();
+
+        // Verify that the subinterpreter has its own cached type objects.
+        // For static types, they should be the same object across interpreters.
+        // See also: https://docs.python.org/3/c-api/typeobj.html#static-types
+        REQUIRE(sub_dict_type.is(dict_type));                 // dict is a static type
+        REQUIRE(sub_ordered_dict_type.is(ordered_dict_type)); // OrderedDict is a static type
+        // For heap types, they are dynamically created per-interpreter.
+        // See also: https://docs.python.org/3/c-api/typeobj.html#heap-types
+        REQUIRE_FALSE(sub_default_dict_type.is(default_dict_type)); // defaultdict is a heap type
+
+        // Set up a weakref callback to detect when the subinterpreter's cached default_dict_type
+        // is destroyed so the gil_safe_call_once_and_store storage is not leaked when the
+        // subinterpreter is shutdown.
+        (void) py::weakref(sub_default_dict_type,
+                           py::cpp_function([&](py::handle weakref) -> void {
+                               sub_default_dict_type_destroyed = true;
+                               weakref.dec_ref();
+                           }))
+            .release();
+    }
+
+    // Back in main interpreter, verify main's value is unchanged
+    auto &main_value_after = storage.get_stored();
+    REQUIRE(main_value_after.cast<int64_t>() == main_interp_id);
+
+    // Verify that the types cached in main are unchanged
+    py::object dict_type_after = get_dict_type_object();
+    py::object ordered_dict_type_after = get_ordered_dict_type_object();
+    py::object default_dict_type_after = get_default_dict_type_object();
+    REQUIRE(dict_type_after.is(dict_type));
+    REQUIRE(ordered_dict_type_after.is(ordered_dict_type));
+    REQUIRE(default_dict_type_after.is(default_dict_type));
+
+    // Verify that the subinterpreter's cached default_dict_type was destroyed
+    REQUIRE(sub_default_dict_type_destroyed);
+
+    unsafe_reset_internals_for_single_interpreter();
+}
+
+#    ifdef Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
+TEST_CASE("Per-Subinterpreter GIL") {
+    auto main_int
+        = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+
+    std::atomic<int> started, sync, failure;
+    started = 0;
+    sync = 0;
+    failure = 0;
+
+// REQUIRE throws on failure, so we can't use it within the thread
+#        define T_REQUIRE(status)                                                                 \
+            do {                                                                                  \
+                assert(status);                                                                   \
+                if (!(status))                                                                    \
+                    ++failure;                                                                    \
+            } while (0)
+
+    auto &&thread_main = [&](int num) {
+        while (started == 0)
+            std::this_thread::sleep_for(std::chrono::microseconds(1));
+        ++started;
+
+        py::gil_scoped_acquire gil;
+
+        // we have the GIL, we can access the main interpreter
+        auto t_int
+            = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+        T_REQUIRE(t_int == main_int);
+        py::module_::import("external_module").attr("multi_interp") = "1";
+
+        auto sub = py::subinterpreter::create();
+
+        {
+            py::subinterpreter_scoped_activate sguard{sub};
+
+            py::list(py::module_::import("sys").attr("path")).append(py::str("."));
+
+            // we have switched to the new interpreter and released the main gil
+
+            // trampoline_module did not provide the per_interpreter_gil tag, so it cannot be
+            // imported
+            bool caught = false;
+            try {
+                py::module_::import("trampoline_module");
+            } catch (pybind11::error_already_set &pe) {
+                T_REQUIRE(pe.matches(PyExc_ImportError));
+                std::string msg(pe.what());
+                T_REQUIRE(msg.find("does not support loading in subinterpreters")
+                          != std::string::npos);
+                caught = true;
+            }
+            T_REQUIRE(caught);
+
+            // widget_module did provide the per_interpreter_gil tag, so it this does not throw
+            try {
+                py::module_::import("widget_module");
+                caught = false;
+            } catch (pybind11::error_already_set &) {
+                caught = true;
+            }
+            T_REQUIRE(!caught);
+
+            // widget_module did provide the per_interpreter_gil tag, so it this does not throw
+            py::module_::import("widget_module");
+
+            T_REQUIRE(!py::hasattr(py::module_::import("external_module"), "multi_interp"));
+            py::module_::import("external_module").attr("multi_interp") = std::to_string(num);
+
+            // wait for something to set sync to our thread number
+            // we are holding our subinterpreter's GIL
+            {
+                py::gil_scoped_release nogil;
+                while (sync != num)
+                    std::this_thread::sleep_for(std::chrono::microseconds(1));
+            }
+
+            // now change it so the next thread can move on
+            ++sync;
+
+            // but keep holding the GIL until after the next thread moves on as well
+            {
+                py::gil_scoped_release nogil;
+                while (sync == num + 1)
+                    std::this_thread::sleep_for(std::chrono::microseconds(1));
+            }
+
+            // one last check before quitting the thread, the internals should be different
+            auto sub_int
+                = py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
+            T_REQUIRE(sub_int != main_int);
+        }
+    };
+#        undef T_REQUIRE
+
+    std::thread t1(thread_main, 1);
+    std::thread t2(thread_main, 2);
+
+    // we spawned two threads, at this point they are both waiting for started to increase
+    ++started;
+
+    // ok now wait for the threads to start
+    while (started != 3)
+        std::this_thread::sleep_for(std::chrono::microseconds(1));
+
+    // we still hold the main GIL, at this point both threads are waiting on the main GIL
+    // IN THE CASE of free threading, the threads are waiting on sync (because there is no GIL)
+
+    // IF the below code hangs in one of the wait loops, then the child thread GIL behavior did not
+    // function as expected.
+    {
+        // release the GIL and allow the threads to run
+        py::gil_scoped_release nogil;
+
+        // the threads are now waiting on the sync
+        REQUIRE(sync == 0);
+
+        // this will trigger thread 1 and then advance and trigger 2 and then advance
+        sync = 1;
+
+        // wait for thread 2 to advance
+        while (sync != 3)
+            std::this_thread::sleep_for(std::chrono::microseconds(1));
+
+        // we know now that thread 1 has run and may be finishing
+        // and thread 2 is waiting for permission to advance
+
+        // so we move sync so that thread 2 can finish executing
+        ++sync;
+
+        // now wait for both threads to complete
+        t1.join();
+        t2.join();
+    }
+
+    // now we have the gil again, sanity check
+    REQUIRE(py::cast<std::string>(py::module_::import("external_module").attr("multi_interp"))
+            == "1");
+
+    unsafe_reset_internals_for_single_interpreter();
+
+    // make sure nothing unexpected happened inside the threads, now that they are completed
+    REQUIRE(failure == 0);
+}
+#    endif // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
+
+#endif // PYBIND11_HAS_SUBINTERPRETER_SUPPORT
diff -Nru pybind11-3.0.1/tests/test_with_catch/test_trampoline.py pybind11-3.0.4/tests/test_with_catch/test_trampoline.py
--- pybind11-3.0.1/tests/test_with_catch/test_trampoline.py	1970-01-01 02:00:00.000000000 +0200
+++ pybind11-3.0.4/tests/test_with_catch/test_trampoline.py	2026-04-19 06:01:51.000000000 +0300
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+import trampoline_module
+
+
+def func():
+    class Test(trampoline_module.test_override_cache_helper):
+        def func(self):
+            return 42
+
+    return Test()
+
+
+def func2():
+    class Test(trampoline_module.test_override_cache_helper):
+        pass
+
+    return Test()
diff -Nru pybind11-3.0.1/tools/pybind11Common.cmake pybind11-3.0.4/tools/pybind11Common.cmake
--- pybind11-3.0.1/tools/pybind11Common.cmake	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tools/pybind11Common.cmake	2026-04-19 06:01:51.000000000 +0300
@@ -41,7 +41,18 @@
     "${pybind11_INCLUDE_DIR}"
     CACHE INTERNAL "Include directory for pybind11 (Python not requested)")
 
-if(CMAKE_CROSSCOMPILING AND PYBIND11_USE_CROSSCOMPILING)
+# CMP0190 prohibits calling FindPython with both Interpreter and Development components
+# when cross-compiling, unless the CMAKE_CROSSCOMPILING_EMULATOR variable is defined.
+if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.1")
+  cmake_policy(GET CMP0190 _pybind11_cmp0190)
+  if(_pybind11_cmp0190 STREQUAL "NEW")
+    set(PYBIND11_USE_CROSSCOMPILING "ON")
+  endif()
+endif()
+
+if(CMAKE_CROSSCOMPILING
+   AND PYBIND11_USE_CROSSCOMPILING
+   AND NOT DEFINED CMAKE_CROSSCOMPILING_EMULATOR)
   set(_PYBIND11_CROSSCOMPILING
       ON
       CACHE INTERNAL "")
diff -Nru pybind11-3.0.1/tools/pybind11GuessPythonExtSuffix.cmake pybind11-3.0.4/tools/pybind11GuessPythonExtSuffix.cmake
--- pybind11-3.0.1/tools/pybind11GuessPythonExtSuffix.cmake	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tools/pybind11GuessPythonExtSuffix.cmake	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 function(pybind11_guess_python_module_extension python)
 
@@ -14,15 +14,23 @@
           STRING
           "Extension suffix for Python extension modules (Initialized from SETUPTOOLS_EXT_SUFFIX)")
   endif()
+
+  # The final extension depends on the system
+  set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
+  if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    set(_PY_BUILD_EXTENSION ".pyd")
+  endif()
+
+  # If running under scikit-build-core, use the SKBUILD_SOABI variable:
+  if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED SKBUILD_SOABI)
+    message(STATUS "Determining Python extension suffix based on SKBUILD_SOABI: ${SKBUILD_SOABI}")
+    set(PYTHON_MODULE_EXT_SUFFIX ".${SKBUILD_SOABI}${_PY_BUILD_EXTENSION}")
+  endif()
+
   # If that didn't work, use the Python_SOABI variable:
   if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED ${python}_SOABI)
     message(
       STATUS "Determining Python extension suffix based on ${python}_SOABI: ${${python}_SOABI}")
-    # The final extension depends on the system
-    set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
-    if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
-      set(_PY_BUILD_EXTENSION ".pyd")
-    endif()
     # If the SOABI already has an extension, use it as the full suffix
     # (used for debug versions of Python on Windows)
     if(${python}_SOABI MATCHES "\\.")
@@ -43,9 +51,9 @@
 
   # If we could not deduce the extension suffix, unset the results:
   if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX)
-    unset(PYTHON_MODULE_DEBUG_POSTFIX PARENT_SCOPE)
-    unset(PYTHON_MODULE_EXTENSION PARENT_SCOPE)
-    unset(PYTHON_IS_DEBUG PARENT_SCOPE)
+    unset(PYTHON_MODULE_DEBUG_POSTFIX CACHE)
+    unset(PYTHON_MODULE_EXTENSION CACHE)
+    unset(PYTHON_IS_DEBUG CACHE)
     return()
   endif()
 
@@ -75,12 +83,12 @@
   # Return results
   set(PYTHON_MODULE_DEBUG_POSTFIX
       "${_PYTHON_MODULE_DEBUG_POSTFIX}"
-      PARENT_SCOPE)
+      CACHE INTERNAL "")
   set(PYTHON_MODULE_EXTENSION
       "${_PYTHON_MODULE_EXTENSION}"
-      PARENT_SCOPE)
+      CACHE INTERNAL "")
   set(PYTHON_IS_DEBUG
       "${_PYTHON_IS_DEBUG}"
-      PARENT_SCOPE)
+      CACHE INTERNAL "")
 
 endfunction()
diff -Nru pybind11-3.0.1/tools/pybind11NewTools.cmake pybind11-3.0.4/tools/pybind11NewTools.cmake
--- pybind11-3.0.1/tools/pybind11NewTools.cmake	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tools/pybind11NewTools.cmake	2026-04-19 06:01:51.000000000 +0300
@@ -106,18 +106,7 @@
   endif()
 endif()
 
-if(NOT _PYBIND11_CROSSCOMPILING)
-  # If a user finds Python, they may forget to include the Interpreter component
-  # and the following two steps require it. It is highly recommended by CMake
-  # when finding development libraries anyway, so we will require it.
-  if(NOT DEFINED ${_Python}_EXECUTABLE)
-    message(
-      FATAL_ERROR
-        "${_Python} was found without the Interpreter component. Pybind11 requires this component."
-    )
-
-  endif()
-
+if(NOT _PYBIND11_CROSSCOMPILING AND DEFINED ${_Python}_EXECUTABLE)
   if(DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST AND NOT ${_Python}_EXECUTABLE STREQUAL
                                                  PYBIND11_PYTHON_EXECUTABLE_LAST)
     # Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed
@@ -190,15 +179,15 @@
     include("${CMAKE_CURRENT_LIST_DIR}/pybind11GuessPythonExtSuffix.cmake")
     pybind11_guess_python_module_extension("${_Python}")
   endif()
-  # When cross-compiling, we cannot query the Python interpreter, so we require
-  # the user to set these variables explicitly.
   if(NOT DEFINED PYTHON_IS_DEBUG
      OR NOT DEFINED PYTHON_MODULE_EXTENSION
      OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
     message(
       FATAL_ERROR
-        "When cross-compiling, you should set the PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX \
-        variables appropriately before loading pybind11 (e.g. in your CMake toolchain file)")
+        "A Python interpreter was not found, or you are cross-compiling, and the "
+        "PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX "
+        "variables could not be guessed. Set these variables appropriately before "
+        "loading pybind11 (e.g. in your CMake toolchain file)")
   endif()
 endif()
 
@@ -248,10 +237,7 @@
   # files.
   get_target_property(module_target_type ${_Python}::Module TYPE)
   if(ANDROID AND module_target_type STREQUAL INTERFACE_LIBRARY)
-    set_property(
-      TARGET ${_Python}::Module
-      APPEND
-      PROPERTY INTERFACE_LINK_LIBRARIES "${${_Python}_LIBRARIES}")
+    target_link_libraries(${_Python}::Module INTERFACE ${${_Python}_LIBRARIES})
   endif()
 
   set_property(
diff -Nru pybind11-3.0.1/tools/test-pybind11GuessPythonExtSuffix.cmake pybind11-3.0.4/tools/test-pybind11GuessPythonExtSuffix.cmake
--- pybind11-3.0.1/tools/test-pybind11GuessPythonExtSuffix.cmake	2025-08-22 22:57:09.000000000 +0300
+++ pybind11-3.0.4/tools/test-pybind11GuessPythonExtSuffix.cmake	2026-04-19 06:01:51.000000000 +0300
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.15...4.0)
+cmake_minimum_required(VERSION 3.15...4.2)
 
 # Tests for pybind11_guess_python_module_extension
 # Run using `cmake -P tools/test-pybind11GuessPythonExtSuffix.cmake`
@@ -87,6 +87,30 @@
 unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
 unset(ENV{SETUPTOOLS_EXT_SUFFIX})
 
+# Check the priority of the possible suffix sources.
+set(ENV{SETUPTOOLS_EXT_SUFFIX} ".from-setuptools.pyd")
+set(SKBUILD_SOABI "from-skbuild")
+set(Python3_SOABI "from-python3")
+pybind11_guess_python_module_extension("Python3")
+expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-setuptools.pyd")
+
+unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
+unset(ENV{SETUPTOOLS_EXT_SUFFIX})
+pybind11_guess_python_module_extension("Python3")
+expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-skbuild.pyd")
+
+unset(SKBUILD_SOABI)
+pybind11_guess_python_module_extension("Python3")
+expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-python3.pyd")
+
+set(Python3_SOABI "")
+pybind11_guess_python_module_extension("Python3")
+expect_streq("${PYTHON_MODULE_EXTENSION}" ".pyd")
+
+unset(Python3_SOABI)
+pybind11_guess_python_module_extension("Python3")
+expect_streq("${PYTHON_MODULE_EXTENSION}" "")
+
 # macOS
 set(CMAKE_SYSTEM_NAME "Darwin")
 set(CMAKE_SHARED_MODULE_SUFFIX ".so")
