juicedata/juicefs · error

inode %d is not a directory

Error message

inode %d is not a directory

What it means

Thrown while building an absolute path from an inode (path resolution walking up via attr.Parent): the inode exists but its type is not a directory, so it cannot be treated as a directory entry during the parent walk. The metadata engine enforces the invariant that every node in the ancestor chain of a path must be a directory.

Source

Thrown at pkg/meta/base.go:2367

func (m *baseMeta) GetPaths(ctx Context, inode Ino) []string {
	if inode == RootInode {
		return []string{"/"}
	}

	if inode == TrashInode {
		return []string{"/.trash"}
	}

	outside := "path not shown because it's outside of the mounted root"
	getDirPath := func(ino Ino) (string, error) {
		var names []string
		var attr Attr
		for ino != RootInode && ino != m.root {
			if st := m.en.doGetAttr(ctx, ino, &attr); st != 0 {
				return "", fmt.Errorf("getattr inode %d: %s", ino, st)
			}
			if attr.Typ != TypeDirectory {
				return "", fmt.Errorf("inode %d is not a directory", ino)
			}
			var entries []*Entry
			if st := m.en.doReaddir(ctx, attr.Parent, 0, &entries, -1); st != 0 {
				return "", fmt.Errorf("readdir inode %d: %s", ino, st)
			}
			var name string
			for _, e := range entries {
				if e.Inode == ino {
					name = string(e.Name)
					break
				}
			}
			if attr.Parent == RootInode && ino == TrashInode {
				name = TrashName
			}
			if name == "" {
				return "", fmt.Errorf("entry %d/%d not found", attr.Parent, ino)
			}

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Refresh the inode: re-resolve the path from the current directory tree instead of using a stale cached inode
  2. Check the inode type first (getattr) and only pass directory inodes to path resolution
  3. If it occurs without stale handles, run fsck / verify the metadata engine for corruption

Example fix

// before
entries, err := fs.ReadDir(ino) // ino from stale cache
// after
attr, err := fs.GetAttr(ino)
if err == nil && attr.Typ == TypeDirectory {
    entries, err = fs.ReadDir(ino)
}
Defensive patterns

Strategy: validation

Validate before calling

attr, st := meta.GetAttr(ctx, ino)
if st != 0 { return st }
if attr.Typ != meta.TypeDirectory { return errors.New("not a directory") }
// proceed with path resolution

Type guard

func isDir(attr meta.Attr) bool { return attr.Typ == meta.TypeDirectory }

Prevention

When it happens

Trigger: Calling path-building/resolution APIs (e.g. GetPath / getPath via doGetAttr+doReaddir walk) with an inode whose Attr.Typ is not TypeDirectory; typically happens when a stale or corrupted inode is passed, or when the inode was concurrently removed and recreated as a file.

Common situations: Applications holding cached inode handles across a delete+recreate where the new inode is a regular file; operating on the trash or subdirectory-mount root with stale inodes; metadata corruption or mismatched metadata backend state.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/67aa783d20f8acdb. Report an issue: GitHub.