thanos-io/thanos · error

not a block dir

Error message

not a block dir

What it means

upload() verifies the directory name parses as a ULID (the block ID). If base name of bdir is not a valid ULID, the wrapped 'not a block dir' error is returned, since Thanos blocks must live in ULID-named directories.

Solutions

  1. Ensure the directory name is a valid 26-character ULID; regenerate/rename if needed
  2. Only upload directories produced by Thanos/Prometheus block creation (block.UploadPromBlock or Prometheus shipper)
  3. Validate with ulid.Parse(bdir base name) before uploading

Example fix

// before
err := block.Upload(ctx, logger, bkt, "/data/tmp-block", hf, true)
// after
if _, err := ulid.Parse(filepath.Base("/data/tmp-block")); err != nil {
    return fmt.Errorf("refusing upload: %w", err)
}
err = block.Upload(ctx, logger, bkt, "/data/tmp-block", hf, true)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := ulid.Parse(filepath.Base(bdir)); err != nil {
    return fmt.Errorf("%s is not a ULID block dir: %w", bdir, err)
}

Prevention

When it happens

Trigger: Calling block.Upload / UploadPromBlock with a directory whose base name is not a valid ULID (e.g. 'blocks/tmp', 'chunks', an empty name, or a Prometheus block not yet renamed).

Common situations: Uploading a raw Prometheus TSDB data dir; temp/scratch directories; paths with trailing slashes or typos; custom tooling creating non-ULID directory names.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at pkg/block/block.go:123

}

// upload uploads block from given block dir that ends with block id.
// It makes sure cleanup is done on error to avoid partial block uploads.
// TODO(bplotka): Ensure bucket operations have reasonable backoff retries.
// NOTE: Upload updates `meta.Thanos.File` section.
func upload(ctx context.Context, logger log.Logger, bkt objstore.Bucket, bdir string, hf metadata.HashFunc, checkExternalLabels bool, options ...objstore.UploadOption) error {
	df, err := os.Stat(bdir)
	if err != nil {
		return err
	}
	if !df.IsDir() {
		return errors.Errorf("%s is not a directory", bdir)
	}

	// Verify dir.
	id, err := ulid.Parse(df.Name())
	if err != nil {
		return errors.Wrap(err, "not a block dir")
	}

	meta, err := metadata.ReadFromDir(bdir)
	if err != nil {
		// No meta or broken meta file.
		return errors.Wrap(err, "read meta")
	}

	if checkExternalLabels {
		if len(meta.Thanos.Labels) == 0 {
			return errors.New("empty external labels are not allowed for Thanos block.")
		}
	}

	metaEncoded := strings.Builder{}
	meta.Thanos.Files, err = GatherFileStats(bdir, hf, logger)
	if err != nil {
		return errors.Wrap(err, "gather meta file stats")

View on GitHub (pinned to 35b8b99117)