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

  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.