kopia/kopia · error

error opening directory

Error message

error opening directory

What it means

isEmptyDirectory opens the target path with os.Open to test whether it is an empty directory before creating it; if the open itself fails, the error is wrapped as 'error opening directory'. This happens during createDirectory when the restorer needs to know whether an existing directory can be reused.

Solutions

  1. Grant read (and traverse) permission on the target directory to the user running the restore
  2. Check the wrapped errno: fix EACCES via chmod/chown, ENAMETOOLONG via shorter paths
  3. Ensure no concurrent process deletes/moves the target during restore
  4. Run the restore as a user with sufficient privileges (or with elevated permissions when justified)

Example fix

// before
$ kopia restore snap target-dir  # run as unprivileged user
// after
$ sudo chmod a+rx target-dir && kopia restore snap target-dir
Defensive patterns

Strategy: validation

Validate before calling

// Ensure target dir is readable/traversable by current user
fi, err := os.Stat(targetDir)
if err != nil { return err }
if !fi.IsDir() { return fmt.Errorf("%s is not a directory", targetDir) }
if err := syscall.Access(targetDir, unix.R_OK|unix.X_OK); err != nil {
	return fmt.Errorf("no read access to %s: %w", targetDir, err)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "error opening directory") {
	var pe *fs.PathError
	if errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrPermission) {
		// surface permission guidance to user
	}
}

Prevention

When it happens

Trigger: createDirectory calls isEmptyDirectory(name) and os.Open(name) fails — e.g. permission denied on the path, the path is on an unreadable mount, ENAMETOOLONG, or a race removed the directory between stat and open.

Common situations: Restoring into a directory without read permission, restoring over a path whose permissions changed mid-restore, or running as a non-root user against system-owned directories.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/9c50b0378499f123. Report an issue: GitHub.

Appendix: source

Thrown at snapshot/restore/local_fs_output.go:450

		Reader: r,
		cb:     progressCb,
	}

	log(ctx).Debugf("copying file contents to: %v", targetPath)
	targetPath = ospath.SafeLongFilename(targetPath)

	if o.WriteFilesAtomically {
		//nolint:wrapcheck
		return atomicfile.Write(targetPath, rr)
	}

	return write(targetPath, rr, f.Size(), o.FlushFiles, o.copier)
}

func isEmptyDirectory(name string) (bool, error) {
	f, err := os.Open(name) //nolint:gosec
	if err != nil {
		return false, errors.Wrap(err, "error opening directory")
	}

	defer f.Close() //nolint:errcheck

	if _, err = f.Readdirnames(1); errors.Is(err, io.EOF) {
		return true, nil
	}

	return false, errors.Wrap(err, "error reading directory") // Either not empty or error
}

var _ Output = (*FilesystemOutput)(nil)

View on GitHub (pinned to 82495e54b5)