JuliusBrussee/caveman · error
store is nil
Error message
store is nil
What it means
objectstore.Probe is a library-internal guard that rejects a nil Store before attempting its put/get/purge round-trip. The realistic path to a nil store is FromEnv's documented (nil, nil) return when S3_ENDPOINT is unset outside production — callers that skip the two-value nil check then pass the nil interface straight into Probe. It is a programming-error signal, not an infrastructure failure.
Source
Thrown at shared/platform/objectstore/objectstore.go:231
func New(cfg Config) (Store, error) {
endpoint := strings.TrimPrefix(strings.TrimPrefix(cfg.Endpoint, "https://"), "http://")
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
Secure: cfg.UseSSL,
Region: cfg.Region,
})
if err != nil {
return nil, fmt.Errorf("objectstore: minio client: %w", err)
}
return &minioStore{client: client, bucket: cfg.Bucket}, nil
}
// Probe proves the operations production retention needs instead of accepting a
// syntactically valid but unusable bucket configuration. The random probe body
// contains no tenant data and every version is removed before success returns.
func Probe(ctx context.Context, store Store) error {
if store == nil {
return errors.New("store is nil")
}
probe := make([]byte, 32)
if _, err := rand.Read(probe); err != nil {
return fmt.Errorf("generate probe: %w", err)
}
key := "_cave_health/" + base64.RawURLEncoding.EncodeToString(probe)
if err := store.Put(ctx, key, probe, "application/octet-stream"); err != nil {
return err
}
cleaned := false
defer func() {
if !cleaned {
// Keep health checks within their caller deadline. A failed probe may
// leave one random, tenant-free canary for lifecycle cleanup; readiness
// must never hang on an unbounded background delete.
_ = PurgeObject(ctx, store, key)
}
}()View on GitHub (pinned to 766dce6b13)
Solutions
- Check both return values of FromEnv: outside production, nil store with nil error means object storage is intentionally disabled — skip the probe
- Make readiness handlers treat a disabled store as not-applicable rather than probing
- Grep call sites of Probe for direct FromEnv plumbing without an intermediate nil check
Example fix
// before
store, _ := objectstore.FromEnv()
if err := objectstore.Probe(ctx, store); err != nil { ... }
// after
store, err := objectstore.FromEnv()
if err != nil {
return err
}
if store == nil {
return nil // object storage disabled outside production
}
if err := objectstore.Probe(ctx, store); err != nil { ... } Defensive patterns
Strategy: type-guard
Validate before calling
store, err := objectstore.FromEnv()
if err != nil {
return err
}
if store == nil {
return nil // outside production with S3_ENDPOINT unset, storage is disabled
} Type guard
func storeEnabled(s objectstore.Store) bool {
return s != nil
} Try / catch
if err := objectstore.Probe(ctx, store); err != nil {
if err.Error() == "store is nil" {
// caller bug: FromEnv returned (nil, nil) for a disabled store; skip probing
}
return err
} Prevention
- Always handle both return values of FromEnv — (nil, nil) is a documented disabled state, not an error
- Model readiness handlers around optionality: probe only when a store exists
When it happens
Trigger: Calling Probe(ctx, store) where store is nil — typically store, err := objectstore.FromEnv() outside production with S3_ENDPOINT unset, and the err == nil branch (correct here) proceeding to Probe without checking the first return value.
Common situations: Health-check endpoints that unconditionally probe storage; dev environments where FromEnv legitimately returns (nil, nil) but the readiness handler assumes a store always exists; refactors that moved the FromEnv call and dropped the nil guard.
Related errors
- probe bytes did not round-trip
- objectstore: S3_ENDPOINT is required in production
- objectstore: endpoint and bucket are required
- objectstore: access key and secret key are required
- objectstore: production requires TLS
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/a85ae81c529a3e53.
Report an issue: GitHub.