restic/restic · error

unexpected file type %v at %q

Error message

unexpected file type %v at %q

What it means

openFile in the restorer opens an existing destination path with O_WRONLY|O_NOFOLLOW and 0600, then Stats it and refuses anything that is not a regular file; this error names the file type and path. FIFOs, device nodes, sockets and similar special files cannot receive restored file content. (Symlinks fail earlier at open with ELOOP because of O_NOFOLLOW; directories fail with EISDIR.) It fires during restore when the target path for a regular file is occupied by a special file.

Source

Thrown at internal/restorer/fileswriter.go:77

		buckets:              buckets,
		allowRecursiveDelete: allowRecursiveDelete,
		cache:                cache,
	}
}

func openFile(path string) (*os.File, error) {
	f, err := fs.OpenFile(path, fs.O_WRONLY|fs.O_NOFOLLOW, 0600)
	if err != nil {
		return nil, err
	}
	fi, err := f.Stat()
	if err != nil {
		_ = f.Close()
		return nil, err
	}
	if !fi.Mode().IsRegular() {
		_ = f.Close()
		return nil, fmt.Errorf("unexpected file type %v at %q", fi.Mode().Type(), path)
	}
	return f, nil
}

func createFile(path string, createSize int64, sparse bool, allowRecursiveDelete bool) (*os.File, error) {
	f, err := fs.OpenFile(path, fs.O_CREATE|fs.O_WRONLY|fs.O_NOFOLLOW, 0600)
	if err != nil && fs.IsAccessDenied(err) {
		// If file is readonly, clear the readonly flag by resetting the
		// permissions of the file and try again
		// as the metadata will be set again in the second pass and the
		// readonly flag will be applied again if needed.
		if err = fs.ResetPermissions(path); err != nil {
			return nil, err
		}
		if f, err = fs.OpenFile(path, fs.O_WRONLY|fs.O_NOFOLLOW, 0600); err != nil {
			return nil, err
		}
	} else if err != nil && (errors.Is(err, syscall.ELOOP) || errors.Is(err, syscall.EISDIR)) {

View on GitHub (pinned to a80be1478a)

Solutions

  1. Inspect the path: ls -l / stat on the reported target to see the special file type
  2. Remove or relocate the special file, then restore again
  3. Restore into a fresh, empty directory instead of over an existing tree

Example fix

# before
restic restore latest --target /srv/restore
# after
rm -f /srv/restore/var/run/notify.fifo   # the reported special file
restic restore latest --target /srv/restore
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := fs.Lstat(path); err == nil && !fi.Mode().IsRegular() {
    return fmt.Errorf("refusing to restore over %s at %s — remove or move it first", fi.Mode().Type(), path)
}

Type guard

func isRegularFilePath(path string) bool {
    fi, err := os.Lstat(path)
    return err == nil && fi.Mode().IsRegular()
}

Prevention

When it happens

Trigger: Restoring into a destination where a previous process created a named pipe, unix socket or device node at a path that is a regular file in the snapshot; on Windows, reserved device names (NUL, CON, COM1) used as filenames; container/VM images whose extraction created device nodes prematurely.

Common situations: Restoring over an unpacked rootfs image that contains /dev-like nodes; leftover chroot/container build trees; intentionally booby-trapped directories with FIFOs at file paths.

Related errors


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