Tencent/WeKnora · error
encode stale sandbox binding: %w
Error message
encode stale sandbox binding: %w
What it means
Returned by markBindingStale when json.Marshal of the marked copy of the binding (expected with StaleAt set) fails before the conditional Lua write. Like the encode error in Create, this signals a non-JSON-serializable field or a failing custom marshaller on SessionSandboxBinding. Wrapped as 'encode stale sandbox binding: %w'.
Source
Thrown at internal/sandbox/session_binding_redis.go:273
}
}
// markBindingStale writes the marked binding back only while the stored one
// still names the same sandbox.
func (s *RedisSessionSandboxBindingStore) markBindingStale(
ctx context.Context,
key SessionSandboxKey,
expected SessionSandboxBinding,
staleAt time.Time,
) (bool, error) {
if err := validateBindingMatch(key, expected.Provider, expected.SandboxID); err != nil {
return false, err
}
marked := expected
marked.StaleAt = &staleAt
payload, err := json.Marshal(marked)
if err != nil {
return false, fmt.Errorf("encode stale sandbox binding: %w", err)
}
wrote, err := markBindingStaleIfMatchScript.Run(
ctx,
s.client,
[]string{s.bindingKey(key)},
string(expected.Provider),
expected.SandboxID,
payload,
).Int64()
if err != nil {
return false, fmt.Errorf("mark sandbox binding stale: %w", err)
}
return wrote != 0, nil
}
// escapeRedisGlob quotes the characters SCAN's MATCH treats as wildcards. The
// namespace is operator-supplied and only screened for braces and control
// characters, so a namespace containing "*" would otherwise widen the patternView on GitHub (pinned to 988cbb0330)
Solutions
- Read the wrapped json error for the offending type and drop or json:"-" that field
- Ensure StaleAt *time.Time (JSON-safe) rather than a non-standard time type
- Test markBindingStale/InvalidateByConfig in CI with representative bindings after any struct change
Example fix
// before
type SessionSandboxBinding struct {
Provider RemoteProvider
SandboxID string
StaleAt *time.Time
Conn *grpc.ClientConn // not JSON-encodable
}
// after
type SessionSandboxBinding struct {
Provider RemoteProvider
SandboxID string
StaleAt *time.Time
Conn *grpc.ClientConn `json:"-"`
} Defensive patterns
Strategy: validation
Validate before calling
// assert the struct (with StaleAt set) remains encodable after any schema change
func TestMarkedBindingEncodable(t *testing.T) {
stale := time.Now().UTC()
b := sandbox.SessionSandboxBinding{Provider: provider, SandboxID: "sbx-1", StaleAt: &stale}
if _, err := json.Marshal(b); err != nil {
t.Fatalf("marked binding not encodable: %v", err)
}
} Try / catch
ok, err := invalidateConfig(ctx, store, tenantID, configID)
if err != nil && strings.Contains(err.Error(), "encode stale sandbox binding") {
return fmt.Errorf("binding schema cannot be persisted — fix struct and redeploy: %w", err)
} Prevention
- Treat SessionSandboxBinding as a persisted DTO: JSON-native types only
- Add CI round-trip tests covering the marked/stale variant of the struct
- Gate struct changes behind a migration note in code review
- Keep StaleAt as *time.Time; avoid custom time types with non-standard marshalling
When it happens
Trigger: Calling markBindingStale (via InvalidateByConfig) for a binding whose struct, after copying expected and setting StaleAt, cannot be marshaled — unsupported field types, cyclic references, or a MarshalJSON error.
Common situations: Schema change added a runtime/non-serializable field to SessionSandboxBinding; a binding struct value that came from an unusual code path holds an unsupported embedded type; custom marshaller chokes on the *time.Time StaleAt representation.
Related errors
- encode sandbox binding: %w
- WEKNORA_REDIS_NAMESPACE must not contain braces
- WEKNORA_REDIS_NAMESPACE must not contain control characters
- failed to set FAQ metadata: %w
- marshal credentials: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/fadc6e963401cde0.
Report an issue: GitHub.