jax-ml/jax · error · nb::value_error

Cannot enter TracebackScope recursively.

Error message

Cannot enter TracebackScope recursively.

What it means

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.

Source

Thrown at jaxlib/traceback.cc:218

// truncating any tracebacks captured within its scope to the call site.
class TracebackScope {
 public:
  TracebackScope() = default;
  TracebackScope(const TracebackScope& other) = delete;
  TracebackScope(TracebackScope&& other) noexcept = default;
  TracebackScope& operator=(const TracebackScope&) = delete;
  TracebackScope& operator=(TracebackScope&&) noexcept = default;
  ~TracebackScope() {
    if (old_stop_frame_) {
      CHECK(PyGILState_Check());
      old_stop_frame_ = {};
    }
  }

  // Captures the current stop frame and sets a new one at the caller's caller.
  TracebackScope& Enter() {
    if (old_stop_frame_) {
      throw nb::value_error("Cannot enter TracebackScope recursively.");
    }
    old_stop_frame_ = nb::borrow<nb::object>(
        reinterpret_cast<PyObject*>(Traceback::GetStopFrame()));
    PyThreadState* thread_state = PyThreadState_GET();
    if (thread_state) {
      // We get the frame of the caller of the __enter__ method.
      nb::object frame = nb::steal<nb::object>(
          reinterpret_cast<PyObject*>(PyThreadState_GetFrame(thread_state)));
      if (frame) {
        Traceback::SetStopFrame(reinterpret_cast<PyFrameObject*>(frame.ptr()));
      }
    }
    return *this;
  }

  // Restores the previous stop frame.
  void Exit(nb::args args) {
    Traceback::SetStopFrame(

View on GitHub (pinned to 1e1c6a8fc0)

Solutions

  1. Don't nest TracebackScope context managers; use one at the outermost level
  2. Let JAX manage TracebackScope internally and remove manual usage
  3. Refactor so inner code doesn't open its own scope

Example fix

# before
with TracebackScope():
    with TracebackScope():  # recursive enter
        jax.numpy.sin(1.0)

# after
with TracebackScope():
    jax.numpy.sin(1.0)
Defensive patterns

Strategy: validation

Validate before calling

if not applicable

Try / catch

try:
    with TracebackScope():
        run()
except ValueError as e:
    if 'recursively' in str(e):
        run()  # scope already active; just run
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jax-ml/jax@1e1c6a8fc0 (2026-08-27). Data as JSON: /api/errors/d424ca7148c3fbbb. Report an issue: GitHub.