Tencent/WeKnora · error
begin sandbox turn lease: %w
Error message
begin sandbox turn lease: %w
What it means
Returned by RedisSessionSandboxBindingStore.BeginTurn when the beginTurnScript (HINCRBY refs + PEXPIRE) fails to run — Redis unreachable, context cancelled, or server rejection. The chat-turn lease was not opened, so the caller must assume no rebuild allowance exists for the turn. Wrapped as 'begin sandbox turn lease: %w'.
Source
Thrown at internal/sandbox/session_binding_redis.go:320
}
return out.String()
}
// BeginTurn opens a chat-turn lease. The first increment of a session's
// refcount allows the next resolve to rebuild a stale sandbox.
func (s *RedisSessionSandboxBindingStore) BeginTurn(
ctx context.Context,
key SessionSandboxKey,
) error {
if err := key.Validate(); err != nil {
return err
}
ttlMS := sessionTurnLeaseTTL.Milliseconds()
if ttlMS <= 0 {
ttlMS = (30 * time.Minute).Milliseconds()
}
if err := beginTurnScript.Run(ctx, s.client, []string{s.turnKey(key)}, ttlMS).Err(); err != nil {
return fmt.Errorf("begin sandbox turn lease: %w", err)
}
return nil
}
// EndTurn releases one chat-turn lease. The last release drops the lease so
// a later resolve may rebuild a stale sandbox immediately.
func (s *RedisSessionSandboxBindingStore) EndTurn(
ctx context.Context,
key SessionSandboxKey,
) error {
if err := key.Validate(); err != nil {
return err
}
if err := endTurnScript.Run(ctx, s.client, []string{s.turnKey(key)}).Err(); err != nil {
return fmt.Errorf("end sandbox turn lease: %w", err)
}
return nil
}View on GitHub (pinned to 988cbb0330)
Solutions
- Check redis-cli ping and REDIS_* env config
- Ensure ctx has remaining budget when BeginTurn is called — budget a dedicated timeout for the lease step
- Retry BeginTurn once; a duplicate increment is recoverable by a matching EndTurn
- Inspect server logs for OOM/READONLY/NOAUTH and address the server-side condition
Example fix
// before
if err := store.BeginTurn(ctx, key); err != nil { return err }
// after: dedicated short-timeout context + one retry
turnCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err := store.BeginTurn(turnCtx, key)
cancel()
if err != nil {
time.Sleep(100 * time.Millisecond)
turnCtx, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel2()
if rerr := store.BeginTurn(turnCtx, key); rerr != nil {
return fmt.Errorf("open turn: %w", rerr)
}
} Defensive patterns
Strategy: retry
Validate before calling
// ensure the key is valid and Redis reachable before opening the turn
if err := key.Validate(); err != nil { return err }
if err := rdb.Ping(ctx).Err(); err != nil {
return fmt.Errorf("cannot open turn, redis down: %w", err)
} Type guard
func isBeginTurnError(err error) bool {
return err != nil && strings.Contains(err.Error(), "begin sandbox turn lease")
} Try / catch
if err := store.BeginTurn(ctx, key); err != nil {
if isBeginTurnError(err) {
// one retry with a fresh short timeout; a duplicate increment is closed by EndTurn
rctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if rerr := store.BeginTurn(rctx, key); rerr != nil {
return fmt.Errorf("turn lease unavailable: %w", rerr)
}
} else {
return err
}
}
defer func() {
ectx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_ = store.EndTurn(ectx, key)
}() Prevention
- Always pair BeginTurn with a deferred EndTurn on a detached context
- Do not pass a nearly-expired request context into BeginTurn — reserve a dedicated budget
- Monitor Redis availability; turn open failures cluster around Redis incidents
- After credential rotation, restart app instances rather than hot-swapping passwords
When it happens
Trigger: Calling BeginTurn(ctx, key) at chat-turn start when Redis is down or the ctx is already expired/cancelled; connection pool exhaustion under concurrency; server OOM or read-only replica rejecting the write.
Common situations: Redis restarts mid-conversation; per-request timeouts too short so ctx is nearly spent by the time the turn begins; a network partition during peak load; wrong Redis credentials after a rotation.
Related errors
- delete sandbox binding: %w
- mark sandbox binding stale: %w
- end sandbox turn lease: %w
- WEKNORA_REDIS_NAMESPACE must not contain braces
- WEKNORA_REDIS_NAMESPACE must not contain control characters
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/1697f2f5997b08f3.
Report an issue: GitHub.