{"record":{"id":"954efab9bad3a0b2","repo":"Tencent/WeKnora","slug":"read-sandbox-turn-lease-w","errorCode":null,"errorMessage":"read sandbox turn lease: %w","messagePattern":"read sandbox turn lease: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sandbox/session_binding_redis.go","lineNumber":351,"sourceCode":"\t}\n\tif err := endTurnScript.Run(ctx, s.client, []string{s.turnKey(key)}).Err(); err != nil {\n\t\treturn fmt.Errorf(\"end sandbox turn lease: %w\", err)\n\t}\n\treturn nil\n}\n\n// TurnState reports whether a chat turn is open and whether its first\n// resolve may still rebuild a stale sandbox.\nfunc (s *RedisSessionSandboxBindingStore) TurnState(\n\tctx context.Context,\n\tkey SessionSandboxKey,\n) (bool, bool, error) {\n\tif err := key.Validate(); err != nil {\n\t\treturn false, false, err\n\t}\n\tvalues, err := s.client.HGetAll(ctx, s.turnKey(key)).Result()\n\tif err != nil {\n\t\treturn false, false, fmt.Errorf(\"read sandbox turn lease: %w\", err)\n\t}\n\tif len(values) == 0 {\n\t\treturn false, false, nil\n\t}\n\t_ = s.client.PExpire(ctx, s.turnKey(key), sessionTurnLeaseTTL).Err()\n\trefs, _ := strconv.Atoi(values[\"refs\"])\n\tif refs <= 0 {\n\t\treturn false, false, nil\n\t}\n\treturn true, values[\"rebuild\"] == \"1\", nil\n}\n\n// ConsumeTurnRebuild spends the one rebuild allowed for the current turn.\nfunc (s *RedisSessionSandboxBindingStore) ConsumeTurnRebuild(\n\tctx context.Context,\n\tkey SessionSandboxKey,\n) error {\n\tif err := key.Validate(); err != nil {","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/session_binding_redis.go#L333-L369","documentation":"TurnState reads the per-session turn-lease hash from Redis to report whether a chat turn is open and whether its one stale-sandbox rebuild is still unspent. This error wraps any failure of the HGetAll call against the Redis key `weknora:sandbox:session:{<hash>}:turn`, so callers know the turn state could not be determined. It is thrown because acting on stale-binding decisions requires authoritative lease state; guessing would risk tearing down a sandbox mid-turn.","triggerScenarios":"RedisSessionSandboxBindingStore.TurnState is called while Redis is unreachable, the connection is closed/timed out, the key was migrated or is on a read-only replica, auth fails, or the context is cancelled mid-HGetAll.","commonSituations":"Redis outage or failover during a chat turn; wrong REDIS address/password in config; Redis under memory pressure evicting connections; network partition between the WeKnora node and Redis cluster; context deadline exceeded under load.","solutions":["Check Redis connectivity and health (PING) from the WeKnora node and fix connection settings (address, password, TLS) in config.","Inspect the wrapped error (errors.Unwrap / %w chain) to distinguish timeouts, auth errors, and read-only replica errors and fix accordingly.","Retry TurnState with backoff; it is a read and safe to re-issue.","If on a replica, ensure reads go to the primary or enable read-replica support in the Redis client."],"exampleFix":"// before\nopen, canRebuild, err := store.TurnState(ctx, key)\nif err != nil {\n    return fmt.Errorf(\"turn state unavailable: %v\", err)\n}\n// after\nopen, canRebuild, err := store.TurnState(ctx, key)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)\n        defer cancel()\n        open, canRebuild, err = store.TurnState(ctx, key)\n    }\n    if err != nil {\n        return fmt.Errorf(\"turn state unavailable: %w\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"if err := rdb.Ping(ctx).Err(); err != nil {\n    return fmt.Errorf(\"redis unavailable before TurnState: %w\", err)\n}","typeGuard":"func isRedisErr(err error) bool {\n    var rerr *redis.RedisError\n    return errors.As(err, &rerr)\n}","tryCatchPattern":"open, rebuild, err := store.TurnState(ctx, key)\nif err != nil {\n    if isRetryableRedisErr(err) {\n        open, rebuild, err = store.TurnState(ctxWithRetryTimeout, key)\n    }\n    if err != nil { return fmt.Errorf(\"turn state: %w\", err) }\n}","preventionTips":["Health-check Redis before turn processing and degrade gracefully if down.","Set generous Redis timeouts and pool limits for turn-lease operations.","Monitor Redis latency/errors and alert before they surface as turn-lease failures.","Use the same Redis cluster/config across all WeKnora nodes."],"tags":["redis","sandbox","turn-lease"],"backgroundTag":"redis-unreachable","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}