restic/restic · error

path %s: not a directory

Error message

path %s: not a directory

What it means

The subfolder descent can only walk through directories: after finding the component it checks node.Type == NodeTypeDir and node.Subtree != nil. If the component is a file (or a dir without a stored subtree), descent cannot continue and this error is returned.

Source

Thrown at internal/data/tree.go:321

		subfolder = path.Join(subfolder, name)
		tree, err := LoadTree(ctx, repo, *id)
		if err != nil {
			return nil, fmt.Errorf("path %s: %w", subfolder, err)
		}
		finder := NewTreeFinder(tree)
		node, err := finder.Find(name)
		finder.Close()
		if err != nil {
			return nil, fmt.Errorf("path %s: %w", subfolder, err)
		}
		if node == nil {
			if runtime.GOOS == "windows" && strings.Contains(dir, "\\") {
				return nil, fmt.Errorf("path %s: not found; subfolder syntax currently requires forward slashes; check the output of `restic ls` for valid paths", subfolder)
			}
			return nil, fmt.Errorf("path %s: not found", subfolder)
		}
		if node.Type != NodeTypeDir || node.Subtree == nil {
			return nil, fmt.Errorf("path %s: not a directory", subfolder)
		}
		id = node.Subtree
	}
	return id, nil
}

type peekableNodeIterator struct {
	iter  func() (NodeOrError, bool)
	stop  func()
	value *Node
}

func newPeekableNodeIterator(tree TreeNodeIterator) (*peekableNodeIterator, error) {
	iter, stop := iter.Pull(tree)
	it := &peekableNodeIterator{iter: iter, stop: stop}
	err := it.Next()
	if err != nil {
		it.Close()

View on GitHub (pinned to a80be1478a)

Solutions

  1. End the subfolder at the file itself instead of appending more components
  2. If a symlink was meant, resolve it to its real target directory in the snapshot
  3. Run `restic ls <snapshotID> <path>` to confirm which components are directories

Example fix

# before
restic dump latest:docs/readme.txt/extra stdout

# after
restic dump latest:docs/readme.txt stdout
Defensive patterns

Strategy: validation

Validate before calling

// If you drive the tree walk yourself, check node type before descending:
node, err := finder.Find(name)
if err == nil && node != nil && node.Type != data.NodeTypeDir {
	// this is the terminal file; stop appending further components
}

Type guard

func isDirNode(n *data.Node) bool {
	return n != nil && n.Type == data.NodeTypeDir && n.Subtree != nil
}

Prevention

When it happens

Trigger: Resolving "<snapshotID>:a/b/c/d" where 'c' is a regular file, not a directory; or a dir node whose Subtree pointer is nil in the stored tree.

Common situations: Pointing a directory-style subfolder at a file path; treating a symlink to a directory as a traversable directory (its Type is symlink, not dir).

Related errors


AI-assisted analysis of restic/restic@a80be1478a (2026-08-15). Data as JSON: /api/errors/c247fef655c367b6. Report an issue: GitHub.