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

  1. 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.
  2. Verify the key construction: confirm prefix, tenant ID, and exact key string match what Put wrote (log the key on both sides).
  3. Check whether a retention/lifecycle policy or another consumer could have deleted the object between write and read.
  4. If the object should exist, inspect the bucket directly (mc ls / S3 console) to distinguish key mismatch from backend issues.
  5. 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

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


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)