{"record":{"id":"ecc3c14c28d2f662","repo":"juicedata/juicefs","slug":"s-is-not-directory","errorCode":null,"errorMessage":"%s is not directory","messagePattern":"(.+?) is not directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gateway/gateway.go","lineNumber":802,"sourceCode":"\treturn n.mkdirAllUntil(ctx, path.Clean(p), sep)\n}\n\nfunc (n *jfsObjects) mkdirAllInBucket(ctx context.Context, bucket, p string) error {\n\troot := path.Clean(n.path(bucket))\n\tp = path.Clean(p)\n\tif p != root && !strings.HasPrefix(p, strings.TrimSuffix(root, sep)+sep) {\n\t\treturn syscall.EINVAL\n\t}\n\treturn n.mkdirAllUntil(ctx, p, root)\n}\n\nfunc (n *jfsObjects) mkdirAllUntil(ctx context.Context, p, root string) error {\n\tif p == root {\n\t\treturn nil\n\t}\n\tif fi, eno := n.fs.Stat(mctx, p); eno == 0 {\n\t\tif !fi.IsDir() {\n\t\t\treturn fmt.Errorf(\"%s is not directory\", p)\n\t\t}\n\t\treturn nil\n\t}\n\teno := n.fs.Mkdir(mctx, p, 0777, n.gConf.Umask)\n\tif eno != 0 && fs.IsNotExist(eno) {\n\t\tif err := n.mkdirAllUntil(ctx, path.Dir(p), root); err != nil {\n\t\t\treturn err\n\t\t}\n\t\teno = n.fs.Mkdir(mctx, p, 0777, n.gConf.Umask)\n\t}\n\tif eno != 0 && fs.IsExist(eno) {\n\t\teno = 0\n\t}\n\tif eno == 0 {\n\t\treturn nil\n\t}\n\treturn eno\n}","sourceCodeStart":784,"sourceCodeEnd":820,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/gateway/gateway.go#L784-L820","documentation":"In the S3 gateway, mkdirAllUntil verifies that each path component leading to a target exists as a directory via n.fs.Stat; if the path exists but fi.IsDir() is false, it returns '%s is not directory' and aborts creating the directory hierarchy. This protects against silently treating a regular file/symlink as a folder.","triggerScenarios":"An S3 gateway PUT created an object whose key collides with a directory path (e.g. file 'bucket/a' exists and later a PUT to 'bucket/a/b' triggers mkdirAll of 'a'); or mkdirAll/mkdirAllInBucket called on a path occupied by a non-directory inode in the JuiceFS metadata.","commonSituations":"S3 clients mixing key styles — writing object 'dir' (file) and also 'dir/file' (needs 'dir' as directory); leftover files at paths previously used as folders; race between two clients where one creates a file at a directory path.","solutions":["Delete or rename the offending file at that path in the filesystem (or via the S3 gateway), then retry the operation.","Adopt consistent key conventions (trailing-slash-free, no file/dir collisions) in clients writing to the same bucket.","Check for concurrent writers creating conflicting keys and coordinate or namespace them.","If caused by stale/corrupt metadata, run `juicefs gc` or inspect the inode via `juicefs info`."],"exampleFix":"// before\n// object 'photos' exists as file; client PUTs 'photos/img.jpg'\nerr := mkdirAll(ctx, \"photos\") // -> \"photos is not directory\"\n\n// after\n// remove the conflicting file key first\nos.Remove(\"/jfs/photos\")\nerr := mkdirAll(ctx, \"photos\")","handlingStrategy":"validation","validationCode":"// before writing 'dir/file', ensure 'dir' is not an existing file\nif fi, err := fs.Stat(\"dir\"); err == nil && !fi.IsDir() {\n    return fmt.Errorf(\"conflict: %s is a file\", \"dir\")\n}","typeGuard":null,"tryCatchPattern":"err := gw.Put(bucket, key, r)\nif err != nil && strings.Contains(err.Error(), \"is not directory\") {\n    // resolve the file/dir key collision, then retry\n    return resolveCollisionAndRetry(key)\n}","preventionTips":["Use a single consistent key style per bucket (no file and dir at same prefix)","Avoid concurrent writers creating both 'a' and 'a/b' keys","Clean up legacy flat keys before enabling directory-style access","Alert on this error — it signals key-namespace collisions, not transient failures"],"tags":["s3-gateway","mkdir","path-conflict"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}