pinpoint-apm/pinpoint · error · IllegalStateException
cannot leave with trace scope. depth: ${depth}
Error message
cannot leave with trace scope. depth: ${depth} What it means
BoundaryTraceScope.leave() throws IllegalStateException when the scope cannot be legally exited: either the scope is not active (depth <= 0) or nested boundary counting is inconsistent (skippedBoundary != 0 or depth != 1). Boundary scopes track nested entry/exit pairs for a trace; leave() is only valid when exactly one unmatched entry exists. The guard protects the profiler's scope invariant that pushes and pops are balanced.
Source
Thrown at agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/BoundaryTraceScope.java:59
return false;
} else {
depth++;
return true;
}
}
public boolean canLeave() {
if (skippedBoundary == 0 && depth == 1) {
return true;
} else {
skippedBoundary--;
return false;
}
}
public void leave() {
if (!isActive()) {
throw new IllegalStateException("cannot leave with trace scope. depth: " + depth);
}
if (skippedBoundary != 0 || depth != 1) {
throw new IllegalStateException("cannot leave with BOUNDARY trace scope. depth: " + depth);
}
depth--;
}
@Override
public boolean isActive() {
return depth > 0;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("DefaultTraceScope{");
sb.append("name='").append(name).append('\'');
sb.append(", depth=").append(depth);View on GitHub (pinned to 744c3d3075)
Solutions
- Ensure every scope push/enter has exactly one matching leave in a finally block
- Verify push() executes before any early return that later triggers leave()
- Check for double-leave (leave called twice for one scope) in the interceptor chain
- Review recursive/reentrant instrumented methods to ensure nested boundaries are balanced before leaving
Example fix
// before
TraceScope scope = trace.getScope(SCOPE_NAME);
if (scope != null) {
scope.leave();
}
// after
TraceScope scope = trace.getScope(SCOPE_NAME);
if (scope != null && scope.isActive()) {
try {
// instrumented work
} finally {
scope.leave();
}
} Defensive patterns
Strategy: try-catch
Validate before calling
TraceScope scope = trace.getScope(SCOPE_NAME);
if (scope == null || !scope.isActive()) {
return; // nothing to leave
} Type guard
boolean canLeaveSafely(TraceScope scope) {
return scope != null && scope.isActive();
} Try / catch
try {
scope.leave();
} catch (IllegalStateException e) {
logger.warn("Unbalanced trace scope exit: {}", e.getMessage());
} Prevention
- Always pair scope push with leave inside try/finally
- Never call leave() on a path where push() may not have executed
- Watch for double instrumentation of reentrant methods causing nested scopes
- Log scope depth around boundary operations while debugging interceptors
When it happens
Trigger: leave() is called without a matching push/enter (isActive() false), or called while nested boundary entries remain (depth != 1) or boundary skips are outstanding (skippedBoundary != 0) — typically unbalanced interceptor enter/exit, an exception thrown between push and leave, or double-leave on the same scope.
Common situations: Interceptors that call leave() in a path where push() never ran (e.g. an early return before push), exception thrown mid-interception skipping the balanced leave, or recursive instrumentation entering the boundary twice but leaving only once/incorrectly nested.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- unsupported ObjectType:${objectName}
- unknown version :${version}
- Duplicate order found for plugin : ${pluginId}
- Unexpected samplerType: ${samplerType}
- UNSUPPORTED_OPERATION
AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07).
Data as JSON: /api/errors/f394bf85f190c301.
Report an issue: GitHub.