Tencent/WeKnora · error
sandbox: orphan reaper requires a client
Error message
sandbox: orphan reaper requires a client
What it means
ReapOrphanSandboxes deletes unbound sandboxes for a tenant, but it needs a working sandbox store client to list and delete them. Before doing any work it validates its OrphanReaperDeps; if deps.Client is nil it refuses to run with this sentinel-style error. It is a programming/configuration error, not a runtime failure.
Source
Thrown at internal/sandbox/orphan_reaper.go:60
// ConfigID scopes the sweep to one sandbox config. Empty means the
// deployment default config. Sweeping by tenant alone would delete
// sandboxes belonging to a sibling config on the same provider account.
ConfigID string
// Grace protects freshly created sandboxes that may not be bound yet.
// Pass 0 when reaping deliberately (e.g. before a backend switch), where
// every sandbox on the old backend is known to be abandoned.
Grace time.Duration
Now func() time.Time
}
// ReapOrphanSandboxes deletes unbound sandboxes for one tenant and reports how
// many were removed.
func ReapOrphanSandboxes(ctx context.Context, deps OrphanReaperDeps) (int, error) {
if deps.Client == nil {
return 0, fmt.Errorf("sandbox: orphan reaper requires a client")
}
now := deps.Now
if now == nil {
now = time.Now
}
summaries, err := deps.Client.List(ctx, configSandboxFilter(deps.TenantID, deps.ConfigID))
if err != nil {
return 0, fmt.Errorf(
"sandbox: list workspace %d config %q sandboxes: %w",
deps.TenantID, NormalizeConfigID(deps.ConfigID), err)
}
cutoff := now().Add(-deps.Grace)
deleted := 0
for _, summary := range summaries {
if _, bound := deps.BoundIDs[summary.ID]; bound {
continueView on GitHub (pinned to 988cbb0330)
Solutions
- Initialize deps.Client with the sandbox store client returned by the library's constructor before calling ReapOrphanSandboxes
- Guard the call site: skip or reschedule reaping when the client is not yet connected
- In tests, supply a fake/stub client in OrphanReaperDeps instead of a zero-value struct
Example fix
// before
deps := sandbox.OrphanReaperDeps{TenantID: 7, ConfigID: "cfg", Grace: time.Hour}
n, err := sandbox.ReapOrphanSandboxes(ctx, deps)
// after
deps := sandbox.OrphanReaperDeps{Client: client, TenantID: 7, ConfigID: "cfg", Grace: time.Hour}
n, err := sandbox.ReapOrphanSandboxes(ctx, deps) Defensive patterns
Strategy: validation
Validate before calling
if deps.Client == nil {
return errors.New("reaper deps missing client; skipping reap")
}
n, err := sandbox.ReapOrphanSandboxes(ctx, deps) Type guard
func reaperReady(deps sandbox.OrphanReaperDeps) bool { return deps.Client != nil } Prevention
- Construct OrphanReaperDeps through a single constructor that always sets Client
- Fail fast at startup if the client dependency is nil
- In tests, use a shared helper that builds fully-populated deps
When it happens
Trigger: Calling ReapOrphanSandboxes with OrphanReaperDeps{} or any deps value where the Client field was never assigned (constructor skipped, config load failed silently, or a test forgot to stub the client).
Common situations: Wiring a reaper cron job before the Redis/store client is initialized; partially-built dependency structs after a config error path; unit tests constructing deps inline without a fake client.
Related errors
- sandbox: docker client requires a config
- sandbox: docker backend requires an image
- daemon returned no container state
- e2b remote client config is required
- E2BAPIKey is required for the E2B backend
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/d069a1f443defd3c.
Report an issue: GitHub.