{"record":{"id":"a85ae81c529a3e53","repo":"JuliusBrussee/caveman","slug":"store-is-nil","errorCode":null,"errorMessage":"store is nil","messagePattern":"store is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/objectstore/objectstore.go","lineNumber":231,"sourceCode":"func New(cfg Config) (Store, error) {\n\tendpoint := strings.TrimPrefix(strings.TrimPrefix(cfg.Endpoint, \"https://\"), \"http://\")\n\tclient, err := minio.New(endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, \"\"),\n\t\tSecure: cfg.UseSSL,\n\t\tRegion: cfg.Region,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"objectstore: minio client: %w\", err)\n\t}\n\treturn &minioStore{client: client, bucket: cfg.Bucket}, nil\n}\n\n// Probe proves the operations production retention needs instead of accepting a\n// syntactically valid but unusable bucket configuration. The random probe body\n// contains no tenant data and every version is removed before success returns.\nfunc Probe(ctx context.Context, store Store) error {\n\tif store == nil {\n\t\treturn errors.New(\"store is nil\")\n\t}\n\tprobe := make([]byte, 32)\n\tif _, err := rand.Read(probe); err != nil {\n\t\treturn fmt.Errorf(\"generate probe: %w\", err)\n\t}\n\tkey := \"_cave_health/\" + base64.RawURLEncoding.EncodeToString(probe)\n\tif err := store.Put(ctx, key, probe, \"application/octet-stream\"); err != nil {\n\t\treturn err\n\t}\n\tcleaned := false\n\tdefer func() {\n\t\tif !cleaned {\n\t\t\t// Keep health checks within their caller deadline. A failed probe may\n\t\t\t// leave one random, tenant-free canary for lifecycle cleanup; readiness\n\t\t\t// must never hang on an unbounded background delete.\n\t\t\t_ = PurgeObject(ctx, store, key)\n\t\t}\n\t}()","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/shared/platform/objectstore/objectstore.go#L213-L249","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nstore, _ := objectstore.FromEnv()\nif err := objectstore.Probe(ctx, store); err != nil { ... }\n\n// after\nstore, err := objectstore.FromEnv()\nif err != nil {\n\treturn err\n}\nif store == nil {\n\treturn nil // object storage disabled outside production\n}\nif err := objectstore.Probe(ctx, store); err != nil { ... }","handlingStrategy":"type-guard","validationCode":"store, err := objectstore.FromEnv()\nif err != nil {\n\treturn err\n}\nif store == nil {\n\treturn nil // outside production with S3_ENDPOINT unset, storage is disabled\n}","typeGuard":"func storeEnabled(s objectstore.Store) bool {\n\treturn s != nil\n}","tryCatchPattern":"if err := objectstore.Probe(ctx, store); err != nil {\n\tif err.Error() == \"store is nil\" {\n\t\t// caller bug: FromEnv returned (nil, nil) for a disabled store; skip probing\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["objectstore","nil-check","api-misuse","health-check","go"],"backgroundTag":"nil-argument","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}