{"record":{"id":"3b661a8a050193fd","repo":"Tencent/WeKnora","slug":"delete-terminal-sandbox-binding-w","errorCode":null,"errorMessage":"delete terminal sandbox binding: %w","messagePattern":"delete terminal sandbox binding: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sandbox/session_lifecycle.go","lineNumber":215,"sourceCode":"\t// so a later install in the same turn cannot tear the sandbox down.\n\tl.consumeTurnRebuild(ctx, key)\n\n\tif binding != nil {\n\t\thandle, replace, err := l.connectBinding(ctx, *binding)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !replace {\n\t\t\treturn handle, nil\n\t\t}\n\t\tdeleted, err := l.bindings.DeleteIfMatch(\n\t\t\tctx,\n\t\t\tkey,\n\t\t\tbinding.Provider,\n\t\t\tbinding.SandboxID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"delete terminal sandbox binding: %w\", err)\n\t\t}\n\t\tif !deleted {\n\t\t\treturn nil, errors.New(\"terminal sandbox binding changed during replacement\")\n\t\t}\n\t}\n\n\trecovered, ok, err := l.recoverOwnedSandbox(ctx, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ok {\n\t\treturn recovered, nil\n\t}\n\treturn l.createAndBind(ctx, key)\n}\n\nfunc (l *remoteSessionLifecycle) connectBinding(\n\tctx context.Context,","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/session_lifecycle.go#L197-L233","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nhandle, err := lifecycle.Resolve(ctx, key)\nif err != nil { return err }\n// after\nhandle, err := lifecycle.Resolve(ctx, key)\nif err != nil {\n    if strings.Contains(err.Error(), \"delete terminal sandbox binding\") && isRetryable(err) {\n        time.Sleep(2 * time.Second)\n        handle, err = lifecycle.Resolve(ctx, key) // recreate after terminal sandbox\n    }\n}","handlingStrategy":"retry","validationCode":"if err := rdb.Ping(ctx).Err(); err != nil {\n    return fmt.Errorf(\"binding store down before resolve: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"handle, err := lifecycle.Resolve(ctx, key)\nif err != nil && strings.Contains(err.Error(), \"delete terminal sandbox binding\") {\n    if isRetryable(err) {\n        time.Sleep(backoff)\n        handle, err = lifecycle.Resolve(ctx, key)\n    }\n}","preventionTips":["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."],"tags":["redis","sandbox","terminal-state","compare-and-delete"],"backgroundTag":"binding-delete-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}