{"record":{"id":"ec7e89699a1af123","repo":"Tencent/WeKnora","slug":"sandbox-list-workspace-d-config-q-sandboxes-w","errorCode":null,"errorMessage":"sandbox: list workspace %d config %q sandboxes: %w","messagePattern":"sandbox: list workspace (.+?) config %q sandboxes: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sandbox/orphan_reaper.go","lineNumber":69,"sourceCode":"\tGrace time.Duration\n\n\tNow func() time.Time\n}\n\n// ReapOrphanSandboxes deletes unbound sandboxes for one tenant and reports how\n// many were removed.\nfunc ReapOrphanSandboxes(ctx context.Context, deps OrphanReaperDeps) (int, error) {\n\tif deps.Client == nil {\n\t\treturn 0, fmt.Errorf(\"sandbox: orphan reaper requires a client\")\n\t}\n\tnow := deps.Now\n\tif now == nil {\n\t\tnow = time.Now\n\t}\n\n\tsummaries, err := deps.Client.List(ctx, configSandboxFilter(deps.TenantID, deps.ConfigID))\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\n\t\t\t\"sandbox: list workspace %d config %q sandboxes: %w\",\n\t\t\tdeps.TenantID, NormalizeConfigID(deps.ConfigID), err)\n\t}\n\n\tcutoff := now().Add(-deps.Grace)\n\tdeleted := 0\n\tfor _, summary := range summaries {\n\t\tif _, bound := deps.BoundIDs[summary.ID]; bound {\n\t\t\tcontinue\n\t\t}\n\t\tif deps.Grace > 0 && !summary.StartedAt.IsZero() && summary.StartedAt.After(cutoff) {\n\t\t\tcontinue\n\t\t}\n\t\tif err := deps.Client.Delete(ctx, summary.ID); err != nil {\n\t\t\t// Keep going: one undeletable sandbox must not abort the sweep.\n\t\t\tcontinue\n\t\t}\n\t\tdeleted++","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/orphan_reaper.go#L51-L87","documentation":"The reaper lists sandboxes matching the tenant/config filter via deps.Client.List; when the underlying store call fails, the error is wrapped with the workspace ID and normalized config ID so the failed scope is identifiable. This is a wrapped storage/backend error, not raised by the sandbox package logic itself.","triggerScenarios":"ReapOrphanSandboxes invoked with a Client whose List call returns an error — e.g. Redis down, network timeout, auth rejected, or malformed filter arguments like an invalid config ID.","commonSituations":"Store outage during a scheduled reap; wrong Redis address or credentials in the reaper's environment; deleted config whose ID no longer resolves server-side; transient network partitions in CI.","solutions":["Inspect the wrapped cause (%w) for the actual transport/auth failure and fix connectivity or credentials","Retry the reap with backoff, since List failures are often transient","Verify TenantID/ConfigID passed to the reaper match an existing workspace config","Log the wrapped error with the workspace/config scope before alerting"],"exampleFix":"// before\n_, err := sandbox.ReapOrphanSandboxes(ctx, deps) // err surfaces opaque store failure\n// after\nn, err := sandbox.ReapOrphanSandboxes(ctx, deps)\nif err != nil {\n    var nerr net.Error\n    if errors.As(err, &nerr) { scheduleRetry(ctx, deps); return }\n    log.Fatalf(\"reap failed for workspace %d: %v\", deps.TenantID, err)\n}","handlingStrategy":"retry","validationCode":"if err := client.Ping(ctx); err != nil {\n    return fmt.Errorf(\"sandbox store unreachable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"n, err := sandbox.ReapOrphanSandboxes(ctx, deps)\nif err != nil {\n    var nerr net.Error\n    if errors.As(err, &nerr) { return scheduleRetry(deps) }\n    return err\n}","preventionTips":["Ping the store before scheduled reaps","Retry List-dependent operations with exponential backoff","Alert on repeated reap failures with workspace scope included"],"tags":["go","sandbox","storage","error-wrapping"],"backgroundTag":"store-list-failure","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}