{"record":{"id":"2bfa35fdf724dcf4","repo":"thanos-io/thanos","slug":"s-is-not-a-directory","errorCode":null,"errorMessage":"%s is not a directory","messagePattern":"(.+?) is not a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/block/block.go","lineNumber":117,"sourceCode":"}\n\n// UploadPromBlock uploads a TSDB block to the object storage. It assumes\n// the block is used in Prometheus so it doesn't check Thanos external labels.\nfunc UploadPromBlock(ctx context.Context, logger log.Logger, bkt objstore.Bucket, bdir string, hf metadata.HashFunc, options ...objstore.UploadOption) error {\n\treturn upload(ctx, logger, bkt, bdir, hf, false, options...)\n}\n\n// upload uploads block from given block dir that ends with block id.\n// It makes sure cleanup is done on error to avoid partial block uploads.\n// TODO(bplotka): Ensure bucket operations have reasonable backoff retries.\n// NOTE: Upload updates `meta.Thanos.File` section.\nfunc upload(ctx context.Context, logger log.Logger, bkt objstore.Bucket, bdir string, hf metadata.HashFunc, checkExternalLabels bool, options ...objstore.UploadOption) error {\n\tdf, err := os.Stat(bdir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !df.IsDir() {\n\t\treturn errors.Errorf(\"%s is not a directory\", bdir)\n\t}\n\n\t// Verify dir.\n\tid, err := ulid.Parse(df.Name())\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"not a block dir\")\n\t}\n\n\tmeta, err := metadata.ReadFromDir(bdir)\n\tif err != nil {\n\t\t// No meta or broken meta file.\n\t\treturn errors.Wrap(err, \"read meta\")\n\t}\n\n\tif checkExternalLabels {\n\t\tif len(meta.Thanos.Labels) == 0 {\n\t\t\treturn errors.New(\"empty external labels are not allowed for Thanos block.\")\n\t\t}","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/block/block.go#L99-L135","documentation":"upload() stats the given block directory before pushing it to object storage. If the path exists but is a regular file (or other non-directory), it refuses with '%s is not a directory' because Thanos block uploads only operate on ULID-named block directories.","triggerScenarios":"Calling block.Upload / UploadPromBlock with a bdir path that resolves to a file, or a symlink pointing to a file, instead of a block directory.","commonSituations":"Passing a meta.json file path instead of the block dir; path resolved through a symlink; a script variable pointing at the wrong file; a partially deleted directory replaced by a file.","solutions":["Verify the path passed to Upload/UploadPromBlock points to the block directory (containing meta.json), not a file","Run os.Stat and check IsDir() before calling upload","Fix symlinks or stale paths in tooling that computes the block dir"],"exampleFix":"// before\nerr := block.Upload(ctx, logger, bkt, \"path/to/meta.json\", hf, true)\n// after\nif fi, err := os.Stat(\"path/to/block\"); err == nil && fi.IsDir() {\n    err = block.Upload(ctx, logger, bkt, \"path/to/block\", hf, true)\n}","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(bdir)\nif err != nil { return err }\nif !fi.IsDir() { return fmt.Errorf(\"%s is not a directory\", bdir) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass the block directory root, never meta.json itself","Resolve symlinks (filepath.EvalSymlinks) before uploading","Validate paths in scripts that compute block directories"],"tags":["filesystem","upload","validation"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}