Tencent/WeKnora · error

delete terminal sandbox binding: %w

Error message

delete terminal sandbox binding: %w

What it means

When connectBinding finds the existing binding's sandbox is in a terminal state (unrecoverable, e.g. paused-expired or killed), the lifecycle compare-deletes the binding via DeleteIfMatch so the recovery pass can create a fresh sandbox. This error wraps a failure of that DeleteIfMatch call (Redis/connection error). It ensures replacement is atomic: a concurrent change yields the separate "terminal sandbox binding changed during replacement" error instead.

Source

Thrown at internal/sandbox/session_lifecycle.go:215

	// so a later install in the same turn cannot tear the sandbox down.
	l.consumeTurnRebuild(ctx, key)

	if binding != nil {
		handle, replace, err := l.connectBinding(ctx, *binding)
		if err != nil {
			return nil, err
		}
		if !replace {
			return handle, nil
		}
		deleted, err := l.bindings.DeleteIfMatch(
			ctx,
			key,
			binding.Provider,
			binding.SandboxID,
		)
		if err != nil {
			return nil, fmt.Errorf("delete terminal sandbox binding: %w", err)
		}
		if !deleted {
			return nil, errors.New("terminal sandbox binding changed during replacement")
		}
	}

	recovered, ok, err := l.recoverOwnedSandbox(ctx, key)
	if err != nil {
		return nil, err
	}
	if ok {
		return recovered, nil
	}
	return l.createAndBind(ctx, key)
}

func (l *remoteSessionLifecycle) connectBinding(
	ctx context.Context,

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Fix Redis connectivity/auth per the wrapped cause and retry Resolve — the terminal sandbox will be replaced with a fresh one.
  2. Retry with backoff; the operation is safe to re-issue since DeleteIfMatch is conditional.
  3. If the binding store is persistently failing, manually remove the stale terminal binding for the session to unblock creation.
  4. Monitor provider sandbox idle/expiry policies so terminal states are anticipated rather than hit mid-turn.

Example fix

// before
handle, err := lifecycle.Resolve(ctx, key)
if err != nil { return err }
// after
handle, err := lifecycle.Resolve(ctx, key)
if err != nil {
    if strings.Contains(err.Error(), "delete terminal sandbox binding") && isRetryable(err) {
        time.Sleep(2 * time.Second)
        handle, err = lifecycle.Resolve(ctx, key) // recreate after terminal sandbox
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if err := rdb.Ping(ctx).Err(); err != nil {
    return fmt.Errorf("binding store down before resolve: %w", err)
}

Try / catch

handle, err := lifecycle.Resolve(ctx, key)
if err != nil && strings.Contains(err.Error(), "delete terminal sandbox binding") {
    if isRetryable(err) {
        time.Sleep(backoff)
        handle, err = lifecycle.Resolve(ctx, key)
    }
}

Prevention

When it happens

Trigger: Resolve connects to the bound sandbox, Get indicates a terminal/unrecoverable state, and the DeleteIfMatch on (provider, sandboxID) fails because Redis is unreachable, times out, or the context is cancelled during the call.

Common situations: Provider expired or killed the sandbox (idle timeout, host maintenance) and the replacement cleanup collides with a Redis outage; long-lived sessions reconnecting after provider-side teardown during cluster failover.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/3b661a8a050193fd. Report an issue: GitHub.