Tencent/WeKnora · error
scan sandbox bindings: %w
Error message
scan sandbox bindings: %w
What it means
Returned by listTenantBindingKeys (used by InvalidateByConfig) when client.Scan fails while paging through the workspace's binding keys with MATCH pattern and COUNT 200. SCAN errors are rare but occur on connection loss, timeouts, or cluster misrouting. The invalidation pass aborts so no bindings get marked stale. Wrapped as 'scan sandbox bindings: %w'.
Source
Thrown at internal/sandbox/session_binding_redis.go:241
// A single-node Redis (what the container wires) answers this completely. On a
// Redis Cluster, SCAN reaches one node, so bindings living on the others would
// go unmarked and their sessions would keep the previous image until they end.
func (s *RedisSessionSandboxBindingStore) listTenantBindingKeys(
ctx context.Context,
tenantID uint64,
) ([]SessionSandboxKey, error) {
prefix := fmt.Sprintf(
"weknora:sandbox:session:{%s:%d:", s.namespace, tenantID,
)
const suffix = "}:binding"
pattern := escapeRedisGlob(prefix) + "*" + suffix
var keys []SessionSandboxKey
var cursor uint64
for {
batch, next, err := s.client.Scan(ctx, cursor, pattern, redisBindingScanCount).Result()
if err != nil {
return nil, fmt.Errorf("scan sandbox bindings: %w", err)
}
for _, raw := range batch {
sessionID := strings.TrimSuffix(strings.TrimPrefix(raw, prefix), suffix)
key := SessionSandboxKey{TenantID: tenantID, SessionID: sessionID}
if key.Validate() != nil {
continue
}
keys = append(keys, key)
}
if next == 0 {
return keys, nil
}
cursor = next
}
}
// markBindingStale writes the marked binding back only while the stored one
// still names the same sandbox.View on GitHub (pinned to 988cbb0330)
Solutions
- Retry InvalidateByConfig — SCAN with cursor is safe to restart from scratch
- Raise the context timeout so a long SCAN across many keys completes
- Check node health/logs if on Redis Cluster; remember SCAN covers only the node reached — route to master or scan per node
- Verify network stability between app and Redis (look for pooled-conn reset logs)
Example fix
// before
n, err := store.InvalidateByConfig(ctx, tenantID, configID)
if err != nil { return err }
// after: retry with fresh context
var n int
for attempt := 0; attempt < 3; attempt++ {
scanCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
n, err = store.InvalidateByConfig(scanCtx, tenantID, configID)
cancel()
if err == nil { break }
}
if err != nil { return err } Defensive patterns
Strategy: retry
Validate before calling
// pre-check Redis health and give the scan a generous deadline
scanCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
if err := rdb.Ping(scanCtx).Err(); err != nil {
return fmt.Errorf("skip invalidation, redis down: %w", err)
} Type guard
func isScanError(err error) bool {
return err != nil && strings.Contains(err.Error(), "scan sandbox bindings")
} Try / catch
n, err := store.InvalidateByConfig(ctx, tenantID, configID)
if isScanError(err) {
// restart from scratch: SCAN is stateless, safe to retry
n, err = store.InvalidateByConfig(ctx, tenantID, configID)
}
if err != nil { return err } Prevention
- Size the context timeout to the tenant's key count, not the default request timeout
- Avoid Redis Cluster for this store, or scan each master node individually (documented single-node limitation)
- Run InvalidateByConfig from an async job with retries, not inline in a request handler
- Keep namespaces free of glob characters — the store escapes them, but simpler namespaces are safer
When it happens
Trigger: Calling InvalidateByConfig(ctx, tenantID, configID) when: the Redis connection drops mid-scan; ctx deadline exceeded over many batches; Redis Cluster where the node serving the SCAN errors; OOM/auth errors on the server.
Common situations: Very large keyspaces extending the scan past a request timeout; cluster mode where SCAN hits a single node (documented limitation — plus errors when that node fails); transient network blip during config invalidation after rotating an API key.
Related errors
- get sandbox binding: %w
- create sandbox binding: %w
- delete sandbox binding: %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/9f945f87f2ab1548.
Report an issue: GitHub.