juicedata/juicefs · error
%s is not directory
Error message
%s is not directory
What it means
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.
Source
Thrown at pkg/gateway/gateway.go:802
return n.mkdirAllUntil(ctx, path.Clean(p), sep)
}
func (n *jfsObjects) mkdirAllInBucket(ctx context.Context, bucket, p string) error {
root := path.Clean(n.path(bucket))
p = path.Clean(p)
if p != root && !strings.HasPrefix(p, strings.TrimSuffix(root, sep)+sep) {
return syscall.EINVAL
}
return n.mkdirAllUntil(ctx, p, root)
}
func (n *jfsObjects) mkdirAllUntil(ctx context.Context, p, root string) error {
if p == root {
return nil
}
if fi, eno := n.fs.Stat(mctx, p); eno == 0 {
if !fi.IsDir() {
return fmt.Errorf("%s is not directory", p)
}
return nil
}
eno := n.fs.Mkdir(mctx, p, 0777, n.gConf.Umask)
if eno != 0 && fs.IsNotExist(eno) {
if err := n.mkdirAllUntil(ctx, path.Dir(p), root); err != nil {
return err
}
eno = n.fs.Mkdir(mctx, p, 0777, n.gConf.Umask)
}
if eno != 0 && fs.IsExist(eno) {
eno = 0
}
if eno == 0 {
return nil
}
return eno
}View on GitHub (pinned to c9a67b23e8)
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`.
Example fix
// before
// object 'photos' exists as file; client PUTs 'photos/img.jpg'
err := mkdirAll(ctx, "photos") // -> "photos is not directory"
// after
// remove the conflicting file key first
os.Remove("/jfs/photos")
err := mkdirAll(ctx, "photos") Defensive patterns
Strategy: validation
Validate before calling
// before writing 'dir/file', ensure 'dir' is not an existing file
if fi, err := fs.Stat("dir"); err == nil && !fi.IsDir() {
return fmt.Errorf("conflict: %s is a file", "dir")
} Try / catch
err := gw.Put(bucket, key, r)
if err != nil && strings.Contains(err.Error(), "is not directory") {
// resolve the file/dir key collision, then retry
return resolveCollisionAndRetry(key)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/ecc3c14c28d2f662.
Report an issue: GitHub.