kovidgoyal/kitty · error
resolving base symlinks: %w
Error message
resolving base symlinks: %w
What it means
RelativeIfUnder computes whether target lies under base, optionally resolving symlinks first with filepath.EvalSymlinks. This variant fires when EvalSymlinks fails on the base path: the base path does not exist, contains a symlink loop, or is inaccessible.
Source
Thrown at tools/utils/paths.go:349
return
}
// 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, nilView on GitHub (pinned to 6d5d0c4406)
Solutions
- Ensure the base directory exists (os.MkdirAll) before calling with resolveSymlinks=true.
- Fix or remove broken/cyclic symlinks in the base path (readlink -f to diagnose).
- Pass resolveSymlinks=false if symlink semantics are not required.
- Check permissions on each component of the base path.
Example fix
// before rel, inside, err := utils.RelativeIfUnder(outDir, f, true) // after _ = os.MkdirAll(outDir, 0o755) rel, inside, err := utils.RelativeIfUnder(outDir, f, true)
Defensive patterns
Strategy: validation
Validate before calling
if _, err := filepath.EvalSymlinks(base); err != nil {
_ = os.MkdirAll(base, 0o755) // create before resolving
} Type guard
func baseResolvable(base string) bool {
_, err := filepath.EvalSymlinks(base)
return err == nil
} Try / catch
rel, inside, err := utils.RelativeIfUnder(base, target, true)
if err != nil && strings.HasPrefix(err.Error(), "resolving base") {
rel, inside, err = utils.RelativeIfUnder(base, target, false)
} Prevention
- MkdirAll the base directory before symlink-resolving calls.
- readlink -f base to detect broken/cyclic symlinks early.
- Keep base paths absolute and existing.
When it happens
Trigger: Calling RelativeIfUnder(base, target, true) where base doesn't exist (e.g. a config dir not yet created), a symlink cycle in the base chain, or permission denied on a path component. On macOS, /tmp vs /private/tmp mismatches also force symlink resolution paths.
Common situations: Applying shell-relative path logic to a not-yet-created output directory, or a base path from user config pointing at a broken symlink.
Related errors
- resolving target symlinks: %w
- abs base: %w
- abs target: %w
- Failed to convert image data output file: %s to absolute pat
- Failed to resolve symlinks in %#v with error: %w
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/690967cd118aa29c.
Report an issue: GitHub.