dbt-labs/dbt-core · error
`EnterGuard` values dropped out of order. Guards returned…
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).
Solutions
- Scope each enter() guard tightly (let _guard = handle.enter(); inside a block) so RAII guarantees reverse-order drop.
- Never store EnterGuard values in collections or fields; only keep them as local bindings.
- Check for mem::forget, ManuallyDrop, or early returns that skip guard drops out of order.
- 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
- Keep EnterGuard values as local RAII bindings; never store them in structs, Vecs, or fields.
- Scope nested enters in inner blocks so inner guards drop first automatically.
- Avoid mem::forget/ManuallyDrop on guards and early returns that juggle multiple guards manually.
- Prefer passing the Handle to code that needs it instead of re-entering repeatedly.
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
- {e} {:?}
- {e}
- inconsistent park state; actual =
- inconsistent park_timeout state; actual =
- Adapter must be configured for the parse phase
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/8495c20c49964150.
Report an issue: GitHub.
Appendix: 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)