{"record":{"id":"0be318149df214c4","repo":"Tencent/WeKnora","slug":"delete-mismatched-provider-binding-w","errorCode":null,"errorMessage":"delete mismatched provider binding: %w","messagePattern":"delete mismatched provider binding: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sandbox/session_lifecycle.go","lineNumber":172,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"check owning session: %w\", err)\n\t}\n\tif !exists {\n\t\tif binding != nil {\n\t\t\terr = l.destroyBindingLocked(ctx, key, *binding)\n\t\t}\n\t\treturn nil, errors.Join(ErrSandboxSessionDeleted, err)\n\t}\n\n\tif binding != nil && binding.Provider != l.client.Provider() {\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 mismatched provider binding: %w\", err)\n\t\t}\n\t\tif !deleted {\n\t\t\treturn nil, errors.New(\"mismatched provider binding changed during replacement\")\n\t\t}\n\t\tbinding = nil\n\t}\n\n\t// A stale binding is one whose sandbox boots an image the config has since\n\t// replaced. Rebuild waits for a turn boundary: the first resolve of a new\n\t// chat turn may destroy and recreate, later resolves of that same turn\n\t// keep the sandbox so /workspace scratch and in-flight execs survive an\n\t// install that landed mid-turn. A resolve with no turn lease (no AgentQA\n\t// in flight) still rebuilds immediately.\n\t//\n\t// Destroying before rebuilding is not optional: the recovery pass below\n\t// adopts any live sandbox carrying this session's metadata, so a surviving\n\t// one would simply be picked up again.\n\tif binding != nil && binding.StaleAt != nil && l.shouldRebuildStaleBinding(ctx, key) {","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/session_lifecycle.go#L154-L190","documentation":"When resolveLocked finds a binding whose Provider differs from the current client's provider, it deletes the stale binding using the compare-and-delete DeleteIfMatch so only the exact old binding is removed. This error wraps a failure of that DeleteIfMatch call (Redis error). It exists to guarantee provider replacement is atomic: if the binding changed concurrently, a separate \"changed during replacement\" error is returned instead.","triggerScenarios":"A session was previously bound to provider A (e.g. after a provider migration or config change) and is now resolved via provider B; the DeleteIfMatch on the old binding's (provider, sandboxID) fails due to a Redis/connection error.","commonSituations":"Switching sandbox providers in config while old sessions still hold bindings from the previous provider, during a Redis outage or failover; partially completed provider migrations.","solutions":["Fix Redis connectivity if the wrapped cause is a store error, then retry Resolve.","If switching providers intentionally, complete the migration during low traffic and retry resolves so mismatched bindings drain.","Verify the binding store replication/cluster is healthy so compare-and-delete ops succeed consistently.","After retries fail, inspect the binding row manually and clear the stale binding to unblock resolution."],"exampleFix":"// before\n// provider switched in config, resolve fails\nhandle, err := lifecycle.Resolve(ctx, key)\n// after\nhandle, err := lifecycle.Resolve(ctx, key)\nif err != nil {\n    var netErr *net.OpError\n    if errors.As(err, &netErr) { // Redis connectivity during mismatch cleanup\n        time.Sleep(backoff)\n        handle, err = lifecycle.Resolve(ctx, key)\n    }\n}","handlingStrategy":"retry","validationCode":"// confirm provider switch is intentional and old bindings exist only for the old provider\nold, err := store.GetBinding(ctx, key)\nif err == nil && old != nil && old.Provider == currentProvider {\n    // no mismatch path will run\n}","typeGuard":null,"tryCatchPattern":"handle, err := lifecycle.Resolve(ctx, key)\nif err != nil && strings.Contains(err.Error(), \"delete mismatched provider binding\") {\n    if isRetryable(err) {\n        time.Sleep(backoff)\n        handle, err = lifecycle.Resolve(ctx, key)\n    }\n}","preventionTips":["Perform provider migrations during quiet windows and drain old bindings before cutover.","Keep the binding Redis cluster healthy (replication, failover drills).","Log binding provider changes to spot migration churn.","Retry resolves rather than manually editing bindings except as a last resort."],"tags":["redis","provider-migration","compare-and-delete"],"backgroundTag":"binding-delete-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}