dbt-labs/dbt-core · error

`EnterGuard` values dropped out of order. Guards returned by

Error message

`EnterGuard` values dropped out of order. Guards returned by `Handle::enter()` must be dropped in the reverse order as they were acquired.

What it means

This EnterGuard tracks the depth of a thread-local task context stack (like tokio's Handle::enter). Guards must be dropped in LIFO order; if a guard's depth doesn't match the current stack depth at drop time, the nesting invariant is violated and the code panics (unless already panicking, in which case it stays silent).

Source

Thrown at crates/dbt-runtime/src/context/current.rs:80

}

impl HandleCell {
    pub(super) const fn new() -> HandleCell {
        HandleCell {
            handle: RefCell::new(None),
            depth: Cell::new(0),
        }
    }
}

impl Drop for SetCurrentGuard {
    fn drop(&mut self) {
        CONTEXT.with(|ctx| {
            let depth = ctx.current.depth.get();

            if depth != self.depth {
                if !std::thread::panicking() {
                    panic!(
                        "`EnterGuard` values dropped out of order. Guards returned by \
                         `Handle::enter()` must be dropped in the reverse order as they \
                         were acquired."
                    );
                } else {
                    // Just return... this will leave handles in a wonky state though...
                    return;
                }
            }

            *ctx.current.handle.borrow_mut() = self.prev.take();
            ctx.current.depth.set(depth - 1);
        });
    }
}

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Scope each enter() guard tightly (let _guard = handle.enter(); inside a block) so RAII guarantees reverse-order drop.
  2. Never store EnterGuard values in collections or fields; only keep them as local bindings.
  3. Check for mem::forget, ManuallyDrop, or early returns that skip guard drops out of order.
  4. If guards must nest, ensure the innermost guard is dropped before the outer one.

Example fix

// before
let g1 = handle.enter();
let g2 = handle.enter();
drop(g1); // wrong order
drop(g2);

// after
let g1 = handle.enter();
{
    let g2 = handle.enter();
    // ... inner work ...
} // g2 dropped first automatically
drop(g1);
Defensive patterns

Strategy: type-guard

Type guard

fn guard_depth_ok(ctx: &Ctx, guard: &EnterGuard) -> bool {
    ctx.current.depth.get() == guard.depth + 1
}

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    let _g = handle.enter();
    run_task();
})).unwrap_or_else(|_| eprintln!("enter scope panicked; guards unwound in order"));

Prevention

When it happens

Trigger: Dropping an EnterGuard out of acquisition order — e.g. storing guards in a collection and dropping them in FIFO order, leaking one guard while dropping an outer one, mem::forget on a guard, or holding a guard across an early return/panic path that unwinds past an inner guard differently.

Common situations: Manually calling Handle::enter() in tests or FFI boundaries and juggling multiple guards; wrapping enter() in a non-RAII pattern; catching a panic inside an entered scope and then dropping guards in the wrong sequence.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/8495c20c49964150. Report an issue: GitHub.