thanos-io/thanos · error

bucket is nil

Error message

bucket is nil

What it means

NewCachingBucket validates that a non-nil objstore.Bucket is supplied before wrapping it. Passing nil would leave CachingBucket with a nil embedded Bucket that panics on first use, so the constructor fails fast with "bucket is nil".

Solutions

  1. Check the error from the underlying bucket client constructor before passing the bucket to NewCachingBucket.
  2. Log/inspect the bucket factory result; ensure the objstore provider (s3/gcs/etc.) config is valid and non-nil.
  3. In tests, always build a real or in-memory objstore.Bucket (e.g. objstore.NewInMemBucket) rather than passing nil.
  4. Fail startup early with a clear message when bucket construction yields nil.

Example fix

// before
bkt, _ := objstore.NewClient(ctx, logger, cfg, reg)
cb, err := cache.NewCachingBucket(bkt, cacheCfg, logger, reg)
// after
bkt, err := objstore.NewClient(ctx, logger, cfg, reg)
if err != nil {
    return errors.Wrap(err, "build bucket client")
}
cb, err := cache.NewCachingBucket(bkt, cacheCfg, logger, reg)
Defensive patterns

Strategy: validation

Validate before calling

if bkt == nil {
    return errors.New("underlying bucket client is nil; check bucket config")
}
cb, err := cache.NewCachingBucket(bkt, cacheCfg, logger, reg)

Type guard

func bucketReady(b objstore.Bucket) bool { return b != nil }

Try / catch

cb, err := cache.NewCachingBucket(bkt, cacheCfg, logger, reg)
if err != nil {
    return errors.Wrap(err, "create caching bucket") // includes 'bucket is nil'
}

Prevention

When it happens

Trigger: Calling NewCachingBucket(nil, cfg, logger, reg), typically when a bucket client construction step upstream returned (nil, nil) or a nil bucket was propagated from a factory/config lookup.

Common situations: Misconfigured bucket client YAML silently producing a nil bucket; ignoring an error from an earlier bucket-constructor call; tests wiring a caching bucket without an underlying bucket.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/658d93231ac7fa50. Report an issue: GitHub.

Appendix: source

Thrown at pkg/store/cache/caching_bucket.go:58

	objstore.Bucket

	cfg    *cache.CachingBucketConfig
	logger log.Logger

	requestedGetRangeBytes *prometheus.CounterVec
	fetchedGetRangeBytes   *prometheus.CounterVec
	refetchedGetRangeBytes *prometheus.CounterVec

	operationConfigs  map[string][]*cache.OperationConfig
	operationRequests *prometheus.CounterVec
	operationHits     *prometheus.CounterVec
}

// NewCachingBucket creates new caching bucket with provided configuration. Configuration should not be
// changed after creating caching bucket.
func NewCachingBucket(b objstore.Bucket, cfg *cache.CachingBucketConfig, logger log.Logger, reg prometheus.Registerer) (*CachingBucket, error) {
	if b == nil {
		return nil, errors.New("bucket is nil")
	}

	cb := &CachingBucket{
		Bucket: b,
		cfg:    cfg,
		logger: logger,

		operationConfigs: map[string][]*cache.OperationConfig{},

		requestedGetRangeBytes: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{
			Name: "thanos_store_bucket_cache_getrange_requested_bytes_total",
			Help: "Total number of bytes requested via GetRange.",
		}, []string{"config"}),
		fetchedGetRangeBytes: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{
			Name: "thanos_store_bucket_cache_getrange_fetched_bytes_total",
			Help: "Total number of bytes fetched because of GetRange operation. Data from bucket is then stored to cache.",
		}, []string{"origin", "config"}),
		refetchedGetRangeBytes: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{

View on GitHub (pinned to 35b8b99117)