Tencent/WeKnora · error

decode sandbox binding: %w

Error message

decode sandbox binding: %w

What it means

This error is returned by RedisSessionSandboxBindingStore.Get when the JSON value stored under the session's binding key in Redis cannot be unmarshaled into SessionSandboxBinding. The binding key holds JSON with no TTL, so the store throws this when the stored value is corrupt, truncated, written by a different schema version, or not JSON at all (e.g. hand-edited or written by another tool). It wraps the underlying json.Unmarshal error with 'decode sandbox binding: %w'.

Source

Thrown at internal/sandbox/session_binding_redis.go:127

// Get returns the current binding, or nil when the session is unbound.
func (s *RedisSessionSandboxBindingStore) Get(
	ctx context.Context,
	key SessionSandboxKey,
) (*SessionSandboxBinding, error) {
	if err := key.Validate(); err != nil {
		return nil, err
	}
	raw, err := s.client.Get(ctx, s.bindingKey(key)).Bytes()
	if errors.Is(err, redis.Nil) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get sandbox binding: %w", err)
	}

	var binding SessionSandboxBinding
	if err := json.Unmarshal(raw, &binding); err != nil {
		return nil, fmt.Errorf("decode sandbox binding: %w", err)
	}
	if err := binding.Validate(key); err != nil {
		return nil, fmt.Errorf("validate sandbox binding: %w", err)
	}
	return &binding, nil
}

// Create stores a validated current-schema binding with SET NX and no
// expiration.
func (s *RedisSessionSandboxBindingStore) Create(
	ctx context.Context,
	key SessionSandboxKey,
	binding SessionSandboxBinding,
) (bool, error) {
	if err := binding.Validate(key); err != nil {
		return false, err
	}
	raw, err := json.Marshal(binding)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Run redis-cli GET 'weknora:sandbox:session:{<namespace>:<tenantID>:<sessionID>}:binding' and inspect whether the value is valid JSON matching the current SessionSandboxBinding schema
  2. If the value is corrupt or legacy, delete it with DEL — Get then returns (nil, nil) as an unbound session and the next resolve recreates the binding
  3. Roll out the current build fully so no old process writes legacy-format bindings mid-flight
  4. If corruption is recurring, check for external writers/scripts touching the weknora:sandbox:* key space and check Redis persistence health (AOF/RDB integrity)

Example fix

// before: error surfaces to caller as opaque decode failure
binding, err := store.Get(ctx, key)
if err != nil { return err }
// after: recover by treating an undecodable binding as unbound and recreating it
binding, err := store.Get(ctx, key)
if err != nil {
    if strings.Contains(err.Error(), "decode sandbox binding") {
        _ = store.DeleteIfMatch(ctx, key, provider, sandboxID)
        created, cerr := store.Create(ctx, key, SessionSandboxBinding{Provider: provider, SandboxID: sandboxID})
        if cerr != nil { return cerr }
        _ = created
        binding, err = store.Get(ctx, key)
    }
    if err != nil { return err }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// best-effort pre-check that the stored value is parseable JSON before relying on Get
func bindingLooksValid(ctx context.Context, rdb *redis.Client, key string) bool {
    raw, err := rdb.Get(ctx, "weknora:sandbox:session:{"+key+"}:binding").Bytes()
    return err == nil && json.Valid(raw)
}

Type guard

func isDecodeBindingError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "decode sandbox binding")
}

Try / catch

binding, err := store.Get(ctx, key)
if isDecodeBindingError(err) {
    // corrupt record: treat session as unbound and let resolve recreate the binding
    log.Printf("corrupt binding for %s: %v", key.SessionID, err)
    binding, err = nil, nil
} else if err != nil {
    return fmt.Errorf("get binding: %w", err)
}

Prevention

When it happens

Trigger: Calling Get(ctx, key) when the Redis string at weknora:sandbox:session:{<ns>:<tenant>:<session>}:binding is corrupt/truncated JSON, was written by an older or newer schema version whose fields no longer unmarshal, was manually overwritten with non-JSON data, or contains types incompatible with the SessionSandboxBinding struct (e.g. StaleAt not a timestamp).

Common situations: Version-skew during a rolling upgrade where an older build wrote a legacy binding format; an operator or cleanup script SET the key directly with wrong content; Redis persistence restored a truncated value after a crash; manual inspection tools (redis-cli) accidentally modified the key.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/1dde49104ffdbf56. Report an issue: GitHub.