kovidgoyal/kitty · error

The items to be diffed should both be either directories or

Error message

The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'

What it means

The diff kitten resolves both positional paths and checks their filesystem types before starting the TUI. It refuses to compare a directory with a regular file because its diff driver (configurable via diff_cmd) operates on two like-typed inputs. The check uses the resolved (symlink-followed) type of each path.

Source

Thrown at kittens/diff/main.go:167

	}
	right, err := get_remote_file(args[1])
	if err != nil {
		return 1, err
	}
	defer func() {
		for _, path := range temp_files {
			os.Remove(path)
		}
	}()
	var left_is_dir, right_is_dir bool
	if left, left_is_dir, err = resolve_path(left); err != nil {
		return 1, err
	}
	if right, right_is_dir, err = resolve_path(right); err != nil {
		return 1, err
	}
	if left_is_dir != right_is_dir {
		return 1, fmt.Errorf("The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'")
	}

	lp, err = loop.New()
	loop.MouseTrackingMode(lp, loop.BUTTONS_AND_DRAG_MOUSE_TRACKING)
	if err != nil {
		return 1, err
	}
	lp.ColorSchemeChangeNotifications()
	h := Handler{left: left, right: right, lp: lp}
	lp.OnInitialize = func() (string, error) {
		lp.SetCursorVisible(false)
		lp.SetCursorShape(loop.BAR_CURSOR, true)
		lp.AllowLineWrapping(false)
		lp.SetWindowTitle(fmt.Sprintf("%s vs. %s", left, right))
		lp.QueryCapabilities()
		h.initialize()
		return "", nil
	}

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Make both sides the same type: either two files or two directories.
  2. If comparing a file to a directory, pick the specific file inside the directory to compare against.
  3. If symlinks are involved, check what they resolve to (ls -l / readlink -f) and point at the real targets.

Example fix

# before
kitty +kitten diff ./config.toml ./backup/

# after
kitty +kitten diff ./config.toml ./backup/config.toml
Defensive patterns

Strategy: validation

Validate before calling

// Go: ensure both sides are the same kind before launching
st1, err1 := os.Stat(left)
st2, err2 := os.Stat(right)
if err1 != nil || err2 != nil {
    log.Fatal("stat failed", err1, err2)
}
if st1.IsDir() != st2.IsDir() {
    log.Fatal("both paths must be files or both directories")
}

Type guard

func sameKind(a, b string) bool {
    sa, ea := os.Stat(a)
    sb, eb := os.Stat(b)
    return ea == nil && eb == nil && sa.IsDir() == sb.IsDir()
}

Try / catch

On error, inspect the message text; if it mentions 'directory to a file', resolve the intended file inside the directory and retry once with corrected paths.

Prevention

When it happens

Trigger: Calling `kitty +kitten diff file.txt somedir/` or `kitty +kitten diff dir/ file.txt`; a symlink on one side resolving to a different type than the other side appears to be; a path that is a socket/fifo/device being classified differently than the plain file on the other side.

Common situations: Scripts that compare `$file` against a backup directory; mistyping one side so it hits a directory; a symlinked config path pointing to a directory while the other side is a file; NFS or FUSE mounts reporting odd file types.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/c6c9e57af3d7d7e4. Report an issue: GitHub.