JuliusBrussee/caveman · warning · ErrNotFound
objectstore: object not found
Error message
objectstore: object not found
What it means
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.
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.
Example fix
// before
body, err := store.Get(ctx, key)
if err != nil {
return fmt.Errorf("fetch artifact: %w", err)
}
// after
body, err := store.Get(ctx, key)
if errors.Is(err, objectstore.ErrNotFound) {
return nil, nil // or regenerate/re-upload
}
if err != nil {
return fmt.Errorf("fetch artifact: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
exists, err := store.Exists(ctx, key)
if err != nil { return err }
if !exists { return nil, nil } Type guard
func isNotFound(err error) bool { return errors.Is(err, objectstore.ErrNotFound) } Try / catch
body, err := store.Get(ctx, key)
switch {
case errors.Is(err, objectstore.ErrNotFound):
return handleMiss(key)
case err != nil:
return fmt.Errorf("get %s: %w", key, err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- objectstore: get
- objectstore: object exceeds read limit
- objectstore: production read/write/delete probe failed
- probe bytes did not round-trip
- caveman agent: baseline model unavailable
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/43483a1f49685566.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/objectstore/objectstore.go:29
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"net/url"
"strings"
"time"
"github.com/JuliusBrussee/caveman/shared/platform/env"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// ErrNotFound is returned by Get/Delete when the key does not exist.
var ErrNotFound = errors.New("objectstore: object not found")
// ErrTooLarge is returned when a remote object exceeds the configured in-memory
// read ceiling. Artifact/capture payloads are bounded on write; enforcing a
// read ceiling too prevents a corrupted or attacker-written bucket object from
// exhausting a service process.
var ErrTooLarge = errors.New("objectstore: object exceeds read limit")
// ErrPagingUnsupported is returned when a caller that must bound listing
// memory is given a backend without a cursor-capable implementation.
var ErrPagingUnsupported = errors.New("objectstore: paged listing unsupported")
// Store is the blob backend contract.
type Store interface {
// Put writes body at key, overwriting any existing object.
Put(ctx context.Context, key string, body []byte, contentType string) error
// Get returns the bytes at key, or ErrNotFound.
Get(ctx context.Context, key string) ([]byte, error)
// Delete removes key (no error if already absent).View on GitHub (pinned to 3ee70a1026)