{"record":{"id":"43483a1f49685566","repo":"JuliusBrussee/caveman","slug":"objectstore-object-not-found","errorCode":null,"errorMessage":"objectstore: object not found","messagePattern":"objectstore: object not found","errorType":"exception","errorClass":"ErrNotFound","httpStatus":null,"severity":"warning","filePath":"shared/platform/objectstore/objectstore.go","lineNumber":29,"sourceCode":"import (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/JuliusBrussee/caveman/shared/platform/env\"\n\t\"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n)\n\n// ErrNotFound is returned by Get/Delete when the key does not exist.\nvar ErrNotFound = errors.New(\"objectstore: object not found\")\n\n// ErrTooLarge is returned when a remote object exceeds the configured in-memory\n// read ceiling. Artifact/capture payloads are bounded on write; enforcing a\n// read ceiling too prevents a corrupted or attacker-written bucket object from\n// exhausting a service process.\nvar ErrTooLarge = errors.New(\"objectstore: object exceeds read limit\")\n\n// ErrPagingUnsupported is returned when a caller that must bound listing\n// memory is given a backend without a cursor-capable implementation.\nvar ErrPagingUnsupported = errors.New(\"objectstore: paged listing unsupported\")\n\n// Store is the blob backend contract.\ntype Store interface {\n\t// Put writes body at key, overwriting any existing object.\n\tPut(ctx context.Context, key string, body []byte, contentType string) error\n\t// Get returns the bytes at key, or ErrNotFound.\n\tGet(ctx context.Context, key string) ([]byte, error)\n\t// Delete removes key (no error if already absent).","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/objectstore/objectstore.go#L11-L47","documentation":"objectstore.ErrNotFound is the sentinel returned by Store.Get, GetMetadata, GetObject, and the internal currentness/lifecycle/task-decision helpers when the requested key does not exist in the bucket. The store treats a missing object as an expected, explicit miss rather than a failure, so callers can errors.Is() against it. Callers like engine/ccr rely on this sentinel to distinguish 'unknown handle' from transport or permission failures and deliberately never guess a recovery.","triggerScenarios":"Calling Get/GetMetadata/GetObject with a key that was never written or was already deleted/purged; scanning a sqlite row that has no matching recovery handle (row.Scan returns sql.ErrNoRows, mapped to this sentinel); an object removed by lifecycle/retention policies before it was read; a typo'd or wrongly-prefixed key.","commonSituations":"Reading an artifact or capture that a retention job already purged; looking up a CCR recovery handle after the store was reset or the database replaced; requesting an object with the wrong tenant/key prefix; a race where one consumer deletes an object another still expects.","solutions":["Check existence first with Exists(ctx, key) or handle the miss explicitly via errors.Is(err, objectstore.ErrNotFound) and treat it as empty/unknown rather than an infrastructure failure.","Verify the key construction: confirm prefix, tenant ID, and exact key string match what Put wrote (log the key on both sides).","Check whether a retention/lifecycle policy or another consumer could have deleted the object between write and read.","If the object should exist, inspect the bucket directly (mc ls / S3 console) to distinguish key mismatch from backend issues.","For the ccr/sqlite path, confirm the recovery handle was actually persisted before the process that later loads it runs."],"exampleFix":"// before\nbody, err := store.Get(ctx, key)\nif err != nil {\n    return fmt.Errorf(\"fetch artifact: %w\", err)\n}\n// after\nbody, err := store.Get(ctx, key)\nif errors.Is(err, objectstore.ErrNotFound) {\n    return nil, nil // or regenerate/re-upload\n}\nif err != nil {\n    return fmt.Errorf(\"fetch artifact: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"exists, err := store.Exists(ctx, key)\nif err != nil { return err }\nif !exists { return nil, nil }","typeGuard":"func isNotFound(err error) bool { return errors.Is(err, objectstore.ErrNotFound) }","tryCatchPattern":"body, err := store.Get(ctx, key)\nswitch {\ncase errors.Is(err, objectstore.ErrNotFound):\n    return handleMiss(key)\ncase err != nil:\n    return fmt.Errorf(\"get %s: %w\", key, err)\n}","preventionTips":["Always branch on errors.Is(err, objectstore.ErrNotFound) before generic error handling","Use Exists() when absence is an expected, normal outcome","Log the exact key on both write and read to catch prefix/tenant mismatches","Account for retention/lifecycle purges when setting expectations about object lifetime"],"tags":["objectstore","s3","not-found"],"backgroundTag":"record-not-found","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}