{"record":{"id":"d424ca7148c3fbbb","repo":"jax-ml/jax","slug":"cannot-enter-tracebackscope-recursively","errorCode":null,"errorMessage":"Cannot enter TracebackScope recursively.","messagePattern":"Cannot enter TracebackScope recursively\\.","errorType":"validation","errorClass":"nb::value_error","httpStatus":null,"severity":"error","filePath":"jaxlib/traceback.cc","lineNumber":218,"sourceCode":"// truncating any tracebacks captured within its scope to the call site.\nclass TracebackScope {\n public:\n  TracebackScope() = default;\n  TracebackScope(const TracebackScope& other) = delete;\n  TracebackScope(TracebackScope&& other) noexcept = default;\n  TracebackScope& operator=(const TracebackScope&) = delete;\n  TracebackScope& operator=(TracebackScope&&) noexcept = default;\n  ~TracebackScope() {\n    if (old_stop_frame_) {\n      CHECK(PyGILState_Check());\n      old_stop_frame_ = {};\n    }\n  }\n\n  // Captures the current stop frame and sets a new one at the caller's caller.\n  TracebackScope& Enter() {\n    if (old_stop_frame_) {\n      throw nb::value_error(\"Cannot enter TracebackScope recursively.\");\n    }\n    old_stop_frame_ = nb::borrow<nb::object>(\n        reinterpret_cast<PyObject*>(Traceback::GetStopFrame()));\n    PyThreadState* thread_state = PyThreadState_GET();\n    if (thread_state) {\n      // We get the frame of the caller of the __enter__ method.\n      nb::object frame = nb::steal<nb::object>(\n          reinterpret_cast<PyObject*>(PyThreadState_GetFrame(thread_state)));\n      if (frame) {\n        Traceback::SetStopFrame(reinterpret_cast<PyFrameObject*>(frame.ptr()));\n      }\n    }\n    return *this;\n  }\n\n  // Restores the previous stop frame.\n  void Exit(nb::args args) {\n    Traceback::SetStopFrame(","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jaxlib/traceback.cc#L200-L236","documentation":"TracebackScope.__enter__ (used to trim JAX internal frames from tracebacks) refuses to nest: entering a scope while another is already active on this thread throws a value error, because it tracks a single saved stop frame.","triggerScenarios":"Using jaxlib.traceback.TracebackScope as a context manager and entering it again inside the with-block on the same thread, e.g. wrapping code that itself opens a TracebackScope.","commonSituations":"Library code that composes JAX calls where both caller and callee apply traceback trimming; user code manually using TracebackScope around jax functions that already use it internally.","solutions":["Don't nest TracebackScope context managers; use one at the outermost level","Let JAX manage TracebackScope internally and remove manual usage","Refactor so inner code doesn't open its own scope"],"exampleFix":"# before\nwith TracebackScope():\n    with TracebackScope():  # recursive enter\n        jax.numpy.sin(1.0)\n\n# after\nwith TracebackScope():\n    jax.numpy.sin(1.0)","handlingStrategy":"validation","validationCode":"if not applicable","typeGuard":null,"tryCatchPattern":"try:\n    with TracebackScope():\n        run()\nexcept ValueError as e:\n    if 'recursively' in str(e):\n        run()  # scope already active; just run\n    else:\n        raise","preventionTips":["Never nest TracebackScope; enter it once at the outermost layer","Let JAX manage its own traceback trimming"],"tags":["jax","traceback","context-manager"],"backgroundTag":"nested-context-manager-enter","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}