Tencent/WeKnora · error
delete sandbox binding: %w
Error message
delete sandbox binding: %w
What it means
Returned by RedisSessionSandboxBindingStore.DeleteIfMatch when the Lua script run (deleteBindingIfMatchScript) fails — the same transport/server conditions as any Redis write: unreachable server, cancelled context, script error, or NOAUTH/READONLY. The conditional delete did not happen. Wrapped as 'delete sandbox binding: %w'. Returning (false, nil) means the key was absent or did not match provider/sandbox ID — not an error.
Source
Thrown at internal/sandbox/session_binding_redis.go:174
// DeleteIfMatch atomically deletes only the expected provider and sandbox ID.
func (s *RedisSessionSandboxBindingStore) DeleteIfMatch(
ctx context.Context,
key SessionSandboxKey,
provider RemoteProvider,
sandboxID string,
) (bool, error) {
if err := validateBindingMatch(key, provider, sandboxID); err != nil {
return false, err
}
deleted, err := deleteBindingIfMatchScript.Run(
ctx,
s.client,
[]string{s.bindingKey(key)},
string(provider),
sandboxID,
).Int64()
if err != nil {
return false, fmt.Errorf("delete sandbox binding: %w", err)
}
return deleted != 0, nil
}
// WithLifecycleLock serializes create, recover, replace, and delete transitions
// across all WeKnora processes sharing Redis.
func (s *RedisSessionSandboxBindingStore) WithLifecycleLock(
ctx context.Context,
key SessionSandboxKey,
fn func(context.Context) error,
) error {
if err := key.Validate(); err != nil {
return err
}
if fn == nil {
return errors.New("sandbox lifecycle lock callback is required")
}
return redislock.WithRenewableLock(View on GitHub (pinned to 988cbb0330)
Solutions
- Retry DeleteIfMatch — it is idempotent (false, nil) when the binding is already gone
- redis-cli ping and check server logs for READONLY/OOM/cluster errors
- Verify ctx is not cancelled before the cleanup call and that timeouts allow the round-trip
- For cluster setups, confirm hash-tag routing keeps the binding key stable across resharding
Example fix
// before
if _, err := store.DeleteIfMatch(ctx, key, provider, sandboxID); err != nil {
return fmt.Errorf("teardown: %w", err)
}
// after: treat transient delete failure as non-fatal, retry once
if _, err := store.DeleteIfMatch(ctx, key, provider, sandboxID); err != nil {
time.Sleep(200 * time.Millisecond)
if _, rerr := store.DeleteIfMatch(ctx, key, provider, sandboxID); rerr != nil {
// binding TTL-less but harmless: resolve re-validates it
log.Printf("deferred binding delete for %s: %v", key.SessionID, rerr)
}
} Defensive patterns
Strategy: retry
Validate before calling
// check the binding exists and matches before issuing a delete
cur, err := store.Get(ctx, key)
if err == nil && cur != nil && cur.Provider == provider && cur.SandboxID == sandboxID {
// safe to call DeleteIfMatch
_ = cur
} Type guard
func isDeleteTransportError(err error) bool {
return err != nil && strings.Contains(err.Error(), "delete sandbox binding")
}
// note: (false, nil) simply means no match — not an error Try / catch
deleted, err := store.DeleteIfMatch(ctx, key, provider, sandboxID)
if isDeleteTransportError(err) {
// idempotent: safe to retry; if it ultimately fails, the leftover binding is harmless
log.Printf("binding delete deferred (self-heals via resolve validation): %v", err)
} Prevention
- Always call DeleteIfMatch, never raw DEL, so concurrent rebinds are not clobbered
- Wrap session teardown in a context with its own timeout independent of the request
- Treat delete errors as non-fatal in cleanup paths (resolve re-validates stale bindings)
- Alert on repeated delete failures — they usually coincide with broader Redis incidents
When it happens
Trigger: Calling DeleteIfMatch(ctx, key, provider, sandboxID) when Redis is down, ctx times out while the script runs, the cluster marks the slot as migrating, or the server rejects the EVAL (OOM, read-only replica, script busy).
Common situations: Failover/sentinel blip during session teardown; context cancelled because the HTTP request was abandoned mid-cleanup; Redis Cluster resharding moved the hash tag's slot; NOSCRIPT after SCRIPT FLUSH (go-redis re-sends automatically, but older versions could surface it).
Related errors
- get sandbox binding: %w
- create sandbox binding: %w
- scan sandbox bindings: %w
- mark sandbox binding stale: %w
- begin sandbox turn lease: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/f9d2e14092fd132d.
Report an issue: GitHub.