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
- Fix Redis connectivity/auth per the wrapped cause and retry Resolve — the terminal sandbox will be replaced with a fresh one.
- Retry with backoff; the operation is safe to re-issue since DeleteIfMatch is conditional.
- If the binding store is persistently failing, manually remove the stale terminal binding for the session to unblock creation.
- 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
- Keep the binding Redis store healthy — every terminal replacement depends on DeleteIfMatch.
- Anticipate provider idle/expiry policies and refresh long-lived sandboxes before they terminate.
- Retry resolves automatically; terminal replacement is safe to re-issue.
- Avoid concurrent lifecycle operations on the same session key from multiple nodes outside the provided locks.
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
- WEKNORA_REDIS_NAMESPACE must not contain braces
- WEKNORA_REDIS_NAMESPACE must not contain control characters
- read sandbox turn lease: %w
- consume sandbox turn rebuild: %w
- delete mismatched provider binding: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/3b661a8a050193fd.
Report an issue: GitHub.