juicedata/juicefs · error

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.

Solutions

  1. Delete or rename the offending file at that path in the filesystem (or via the S3 gateway), then retry the operation.
  2. Adopt consistent key conventions (trailing-slash-free, no file/dir collisions) in clients writing to the same bucket.
  3. Check for concurrent writers creating conflicting keys and coordinate or namespace them.
  4. 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

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.

Appendix: 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)