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 nil

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Re-create the lease scope / LocalMounter instead of reusing a released one.
  2. Ensure Release/Unmount is called only after all work with the scope is finished (use defer with correct ordering).
  3. Serialize access: do not call operations on the scope concurrently with Release; guard with the existing mutex/sync discipline.
  4. 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

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


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/29fc18699503d9af. Report an issue: GitHub.