)]}'
{
  "commit": "05f6e6f6815f91a2df52e270f19f70a5e65c113e",
  "tree": "59f1161c85d92a96430e376c195fba05961808e0",
  "parents": [
    "6dd1756f985816fbd06e13faeac9cda86c6b262e"
  ],
  "author": {
    "name": "ymwang78",
    "email": "wangym@gmail.com",
    "time": "Thu Aug 06 15:05:23 2026 +0800"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Thu Aug 06 00:05:23 2026 -0700"
  },
  "message": "fix(subinterpreter): don\u0027t touch the thread state before create() attaches one (#6127)\n\n`subinterpreter::create()` documents that \"the main interpreter and its GIL\nare not required to be held prior to calling this function\", but its first\nstatement is `error_scope err_scope;`, i.e. `PyErr_Fetch()`, before\n`main_guard` attaches a thread state. With no current `PyThreadState`,\n`PyErr_Fetch()` -\u003e `_PyErr_GetRaisedException(NULL)` dereferences null and\nthe process dies (SIGSEGV; 0xC0000005 on Windows). `~error_scope` is the\nmirror image: it calls `PyErr_Restore()` after `main_guard` has already\nswapped the thread state back away.\n\nTwo ordinary situations reach `create()` with no thread state:\n\n- an embedder that ends its initialization with `PyEval_SaveThread()`,\n  which is the documented way to hand the GIL back after\n  `Py_InitializeFromConfig()`;\n- any worker thread that has never touched Python.\n\nExisting tests never hit this because they all run under the\n`py::scoped_interpreter guard{}` in catch.cpp, which keeps the GIL held on\nthe main thread for the whole run.\n\nMove `error_scope` inside the `main_guard` scope. The case it exists for is\nunaffected: a caller that already holds the main GIL takes\n`subinterpreter_scoped_activate`\u0027s `simple_gil_` fast path, which keeps the\nsame thread state, so its pending error is still saved across\n`Py_NewInterpreterFromConfig()` and restored afterwards. A caller sitting on\nsome other interpreter never had its error indicator touched in the first\nplace, since everything inside the block runs on the main interpreter\u0027s\nthread state and `PyThreadState_Swap()` does not move error indicators. It\nalso makes the `pybind11_fail()` path unwind in a safer order: `~error_scope`\nnow runs while `main_guard` is still alive.\n\nAdd \"Create Subinterpreter without a thread state\", covering both a thread\nthat dropped its thread state via `gil_scoped_release` and a thread that\nnever had one. It segfaults without the fix and passes with it.\n\nVerified on Windows / MSVC 14.51 / CPython 3.13.14: test_with_catch goes\nfrom 33 to 34 test cases, all passing.\n\nAssisted-by: ClaudeCode:claude-opus-5",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "def101ff6233801d55726d6e6db3e77bebc0060f",
      "old_mode": 33188,
      "old_path": "include/pybind11/subinterpreter.h",
      "new_id": "f1e4e443616bc1bad3dfa47ae0b8c7078c2ae685",
      "new_mode": 33188,
      "new_path": "include/pybind11/subinterpreter.h"
    },
    {
      "type": "modify",
      "old_id": "3af100f2a9272a72802fe5cf6152b2f36adec5b0",
      "old_mode": 33188,
      "old_path": "tests/test_with_catch/test_subinterpreter.cpp",
      "new_id": "570519e641d4c455c3424c1361f51831c5f8fa00",
      "new_mode": 33188,
      "new_path": "tests/test_with_catch/test_subinterpreter.cpp"
    }
  ]
}
