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
- End the subfolder at the file itself instead of appending more components
- If a symlink was meant, resolve it to its real target directory in the snapshot
- 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
- Treat files as leaves: never append path components after a filename
- Use `restic ls <snap>:<dir>` to confirm which components are directories
- Remember symlinks are NodeTypeSymlink, not traversable directories
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
- path %s: %w
- path %s: not found
- node %q, last %q: %w
- path %s: not found; subfolder syntax currently requires forw
- unexpected file type %v at %q
AI-assisted analysis of restic/restic@a80be1478a (2026-08-15).
Data as JSON: /api/errors/c247fef655c367b6.
Report an issue: GitHub.