{"record":{"id":"fadc6e963401cde0","repo":"Tencent/WeKnora","slug":"encode-stale-sandbox-binding-w","errorCode":null,"errorMessage":"encode stale sandbox binding: %w","messagePattern":"encode stale sandbox binding: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sandbox/session_binding_redis.go","lineNumber":273,"sourceCode":"\t}\n}\n\n// markBindingStale writes the marked binding back only while the stored one\n// still names the same sandbox.\nfunc (s *RedisSessionSandboxBindingStore) markBindingStale(\n\tctx context.Context,\n\tkey SessionSandboxKey,\n\texpected SessionSandboxBinding,\n\tstaleAt time.Time,\n) (bool, error) {\n\tif err := validateBindingMatch(key, expected.Provider, expected.SandboxID); err != nil {\n\t\treturn false, err\n\t}\n\tmarked := expected\n\tmarked.StaleAt = &staleAt\n\tpayload, err := json.Marshal(marked)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"encode stale sandbox binding: %w\", err)\n\t}\n\twrote, err := markBindingStaleIfMatchScript.Run(\n\t\tctx,\n\t\ts.client,\n\t\t[]string{s.bindingKey(key)},\n\t\tstring(expected.Provider),\n\t\texpected.SandboxID,\n\t\tpayload,\n\t).Int64()\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"mark sandbox binding stale: %w\", err)\n\t}\n\treturn wrote != 0, nil\n}\n\n// escapeRedisGlob quotes the characters SCAN's MATCH treats as wildcards. The\n// namespace is operator-supplied and only screened for braces and control\n// characters, so a namespace containing \"*\" would otherwise widen the pattern","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/session_binding_redis.go#L255-L291","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ntype SessionSandboxBinding struct {\n    Provider  RemoteProvider\n    SandboxID string\n    StaleAt   *time.Time\n    Conn      *grpc.ClientConn // not JSON-encodable\n}\n// after\ntype SessionSandboxBinding struct {\n    Provider  RemoteProvider\n    SandboxID string\n    StaleAt   *time.Time\n    Conn      *grpc.ClientConn `json:\"-\"`\n}","handlingStrategy":"validation","validationCode":"// assert the struct (with StaleAt set) remains encodable after any schema change\nfunc TestMarkedBindingEncodable(t *testing.T) {\n    stale := time.Now().UTC()\n    b := sandbox.SessionSandboxBinding{Provider: provider, SandboxID: \"sbx-1\", StaleAt: &stale}\n    if _, err := json.Marshal(b); err != nil {\n        t.Fatalf(\"marked binding not encodable: %v\", err)\n    }\n}","typeGuard":null,"tryCatchPattern":"ok, err := invalidateConfig(ctx, store, tenantID, configID)\nif err != nil && strings.Contains(err.Error(), \"encode stale sandbox binding\") {\n    return fmt.Errorf(\"binding schema cannot be persisted — fix struct and redeploy: %w\", err)\n}","preventionTips":["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"],"tags":["json-encode","serialization","go","redis"],"backgroundTag":"json-marshal-unsupported-type","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}