dagger/dagger · error
operation lease scope already released
Error message
operation lease scope already released
What it means
lazyLeaseScope.ensure (engine/snapshots/lease.go:75) returns this error when code attempts to use a lease scope after its Unmount/Release was called. Once released, the scope can no longer attach a lease to the context, so any subsequent ensure fails immediately.
Source
Thrown at engine/snapshots/lease.go:75
func WithoutLazyLease(ctx context.Context) context.Context {
if lazyLeaseFromContext(ctx) == nil {
return ctx
}
return context.WithValue(ctx, lazyLeaseScopeKey{}, withoutLazyLeaseScope{})
}
func lazyLeaseFromContext(ctx context.Context) *lazyLeaseScope {
scope, _ := ctx.Value(lazyLeaseScopeKey{}).(*lazyLeaseScope)
return scope
}
func (s *lazyLeaseScope) ensure(ctx context.Context) (context.Context, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.released {
return ctx, fmt.Errorf("operation lease scope already released")
}
if s.lease != nil {
return leases.WithLease(ctx, s.lease.l.ID), nil
}
lease, leaseCtx, err := NewLease(ctx, s.lm, s.opts...)
if err != nil {
return ctx, err
}
s.lease = lease
return leaseCtx, nil
}
func (s *lazyLeaseScope) release(ctx context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.released {
return nilView on GitHub (pinned to 82ba2681db)
Solutions
- Re-create the lease scope / LocalMounter instead of reusing a released one.
- Ensure Release/Unmount is called only after all work with the scope is finished (use defer with correct ordering).
- Serialize access: do not call operations on the scope concurrently with Release; guard with the existing mutex/sync discipline.
- If this appears in tests, obtain a fresh scope per test case rather than sharing a fixture-released one.
Example fix
// before mounter, _ := NewLocalMounter(ctx, ...) target, _ := mounter.Mount(ctx) mounter.Unmount(ctx) target2, err := mounter.Mount(ctx) // operation lease scope already released // after mounter, _ := NewLocalMounter(ctx, ...) target, err := mounter.Mount(ctx) // ... use target ... defer mounter.Unmount(ctx) // release exactly once, after last use
Defensive patterns
Strategy: type-guard
Validate before calling
// track release state yourself before reuse
type guardedScope struct {
mu sync.Mutex
released bool
}
func (g *guardedScope) ensureUsable() error {
g.mu.Lock(); defer g.mu.Unlock()
if g.released {
return errors.New("scope already released; create a new one")
}
return nil
} Type guard
func scopeUsable(mu *sync.Mutex, released *bool) bool {
mu.Lock(); defer mu.Unlock()
return !*released
} Try / catch
target, err := mounter.Mount(ctx)
if err != nil && strings.Contains(err.Error(), "operation lease scope already released") {
// recreate scope instead of failing
mounter, err = NewLocalMounter(ctx, ref)
if err == nil {
target, err = mounter.Mount(ctx)
}
} Prevention
- Always release via defer immediately after Mount succeeds
- Never reuse a mounter/scope after Unmount; construct a new one
- Serialize Release with all other scope operations
When it happens
Trigger: Calling any operation that goes through lazyLeaseScope (e.g. a mount or snapshot access via EnsureLease) after the scope's Release was already invoked — typically use-after-release of a LocalMounter/lease-managed resource or concurrent access racing Release.
Common situations: Calling Unmount then re-using the same LocalMounter/lease scope; goroutine race where one goroutine releases while another still resolves the context; long-lived references to a mount kept beyond its cleanup.
Related errors
- evaluate: %w: %q
- acquire operation lease: %w
- missing lease requirement for adopt
- ensure lease for snapshot diff
- verify modified files: %w
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/29fc18699503d9af.
Report an issue: GitHub.