kovidgoyal/kitty · error

Failed to diff %s vs. %s with errors: %s

Error message

Failed to diff %s vs. %s with errors:
%s

What it means

do_diff shells out to the configured diff command via run_diff; when the command reports failure (non-zero exit other than the expected 'differences found' code, or the wrapper signals an error), the kitten surfaces this error embedding both paths and the raw output from the command. It means the external diff process itself failed, not that differences exist.

Source

Thrown at kittens/diff/patch.go:596

		c := exec.Command(cmd[0], cmd[1:]...)
		stdout, stderr := bytes.Buffer{}, bytes.Buffer{}
		c.Stdout, c.Stderr = &stdout, &stderr
		err = c.Run()
		if err != nil {
			var e *exec.ExitError
			if errors.As(err, &e) && e.ExitCode() == 1 {
				return true, true, stdout.String(), nil
			}
			return false, false, stderr.String(), err
		}
		return true, false, stdout.String(), nil
	}
}

func do_diff(file1, file2 string, context_count int) (ans *Patch, err error) {
	ok, _, raw, err := run_diff(file1, file2, context_count)
	if !ok {
		return nil, fmt.Errorf("Failed to diff %s vs. %s with errors:\n%s", file1, file2, raw)
	}
	if err != nil {
		return
	}
	left_lines, err := lines_for_path(file1)
	if err != nil {
		return
	}
	right_lines, err := lines_for_path(file2)
	if err != nil {
		return
	}
	ans, err = parse_patch(raw, left_lines, right_lines)
	return
}

type diff_job struct{ file1, file2 string }

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Read the embedded raw output in the message: it is the external tool's own error text and names the real cause.
  2. Verify diff_cmd exists and is executable in kitty's environment (`which` inside kitty shell).
  3. Make your custom diff wrapper exit 0 for 'same', 1 for 'differences', and >=2 only for genuine errors, GNU diff-style.
  4. Handle files the tool can't process (skip binaries) or quote paths properly in the wrapper.

Example fix

# before (custom diff_cmd wrapper)
#!/bin/sh
exec my-differ "$1" "$2"  # exits 3 on any diff

# after
#!/bin/sh
exec my-differ "$1" "$2"
rc=$?
[ $rc -eq 3 ] && exit 1
exit $rc
Defensive patterns

Strategy: fallback

Validate before calling

// Go: pre-flight the configured diff command
if _, err := exec.LookPath(diffCmd); err != nil {
    return fmt.Errorf("diff_cmd %q not found in PATH", diffCmd)
}

Try / catch

Catch errors from do_diff; parse the embedded tool output to classify the failure. For transient issues (missing file mid-compare) retry once; for exit-code conventions, fall back to GNU diff; otherwise surface the tool's stderr verbatim.

Prevention

When it happens

Trigger: `diff_cmd` in kitty.conf pointing to a missing or non-executable binary; the diff command erroring on its inputs (binary files with a tool that refuses them, permission denied, missing file, out of memory); a wrapper script exiting non-zero and printing diagnostics to stderr which get captured into the message.

Common situations: Custom diff_cmd wrappers returning the wrong exit codes (the kitten expects 0/1 a la GNU diff; 2+ means error); diffing files the configured tool can't handle (binary, huge, special paths with spaces if unquoted); PATH differences between interactive shell and kitty's environment.

Related errors


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