kovidgoyal/kitty · error

resolving target symlinks: %w

Error message

resolving target symlinks: %w

What it means

RelativeIfUnder with resolveSymlinks=true calls filepath.EvalSymlinks on both paths. This error means the target path could not be resolved: it does not exist, is a dangling symlink, has a symlink loop, or a component is not readable. The wrapped error carries the underlying *PathError cause.

Source

Thrown at tools/utils/paths.go:352

// RelativeIfUnder returns the path to 'target' relative to 'base' if and only if
// target is inside base. It returns the relative path, a boolean indicating
// whether target is inside base, and an error.
//
// If resolveSymlinks is true, both base and target are run through filepath.EvalSymlinks
// before containment checks. If base == target the function returns "." and true.
//
// Notes:
//   - This uses filepath.Rel and then checks for leading ".." components to determine
//     whether the returned relative path escapes the base directory.
//   - On Windows behaviour is consistent with filepath semantics.
func RelativeIfUnder(base, target string, resolveSymlinks bool) (rel string, inside bool, err error) {
	// Optionally resolve symlinks first
	if resolveSymlinks {
		if base, err = filepath.EvalSymlinks(base); err != nil {
			return "", false, fmt.Errorf("resolving base symlinks: %w", err)
		}
		if target, err = filepath.EvalSymlinks(target); err != nil {
			return "", false, fmt.Errorf("resolving target symlinks: %w", err)
		}
	}

	// Make absolute and clean
	if base, err = filepath.Abs(base); err != nil {
		return "", false, fmt.Errorf("abs base: %w", err)
	}
	if target, err = filepath.Abs(target); err != nil {
		return "", false, fmt.Errorf("abs target: %w", err)
	}

	// On Windows the volume (drive letter) must match. If they don't, the path is not inside.
	if runtime.GOOS == "windows" {
		if !strings.EqualFold(filepath.VolumeName(base), filepath.VolumeName(target)) {
			return "", false, nil
		}
	}

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Create or touch the target file first, or resolve an existing ancestor directory instead.
  2. Repair the dangling symlink (ls -l to inspect, then relink or remove).
  3. Use resolveSymlinks=false when the target may legitimately not exist yet.
  4. Inspect the wrapped error with errors.Unwrap to distinguish ENOENT/ELOOP/EACCES.

Example fix

// before
rel, inside, err := utils.RelativeIfUnder(base, target, true)
// after
rel, inside, err := utils.RelativeIfUnder(base, target, false)
Defensive patterns

Strategy: fallback

Validate before calling

if _, err := os.Lstat(target); err != nil {
    // target missing/dangling; either create it or skip resolution
}

Type guard

func targetResolvable(target string) bool {
    _, err := filepath.EvalSymlinks(target)
    return err == nil
}

Try / catch

rel, inside, err := utils.RelativeIfUnder(base, target, true)
if err != nil {
    rel, inside, err = utils.RelativeIfUnder(base, target, false)
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Passing a target that is a dangling symlink or a not-yet-created file with resolveSymlinks=true; symlink cycles; EACCES on a path component.

Common situations: Comparing a planned output file path against a base directory before the file exists; symlinked home directories on macOS; NFS stale handles.

Related errors


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