kovidgoyal/kitty · error
%s is not a directory while resolving symlinks in %s
Error message
%s is not a directory while resolving symlinks in %s
What it means
Raised by EvalSymlinksThatExist while walking a path component-by-component: it encountered a non-directory entry at a position that still has remaining path segments. In other words, an intermediate component of the path exists as a regular file (or other non-dir, non-symlink node), so it cannot be descended into to resolve the rest of the path. ExtractAllFromTar hits this when a tar entry's target path routes through a file.
Source
Thrown at tools/utils/tar.go:110
dest += path[start:end]
// Resolve symlink.
fi, err := os.Lstat(dest)
if err != nil {
if os.IsNotExist(err) {
if end < len(path) {
dest += path[end:]
}
return filepath.Clean(dest), nil
}
return "", err
}
if fi.Mode()&fs.ModeSymlink == 0 {
if !fi.Mode().IsDir() && end < len(path) {
return "", fmt.Errorf("%s is not a directory while resolving symlinks in %s", dest, path)
}
continue
}
// Found symlink.
linksWalked++
if linksWalked > 255 {
return "", fmt.Errorf("EvalSymlinksThatExist: too many symlinks in %s", path)
}
link, err := os.Readlink(dest)
if err != nil {
return "", err
}
if isWindowsDot && !filepath.IsAbs(link) {
// On Windows, if "." is a relative symlink,View on GitHub (pinned to 6d5d0c4406)
Solutions
- Clear or use a fresh destination directory before extraction (rm -rf the previous extraction target).
- Inspect the tar entry list for a name used both as file and directory; fix the archive or filter entries.
- Ensure the destination path itself doesn't traverse an existing regular file.
- If extracting programmatically, pre-create needed directories and skip/merge conflicting file entries.
Example fix
// before
err := ExtractAllFromTar(tarReader, "build/out") // build/out is an old regular file
// → "build/out is not a directory while resolving symlinks in build/out/pkg/file.go"
// after
os.RemoveAll("build/out")
os.MkdirAll("build/out", 0o755)
err := ExtractAllFromTar(tarReader, "build/out") Defensive patterns
Strategy: validation
Validate before calling
func destIsCleanDir(dest string) bool {
fi, err := os.Lstat(dest)
if os.IsNotExist(err) { return true }
return err == nil && fi.IsDir()
} Type guard
func extractionPathClear(dest string) error {
fi, err := os.Lstat(dest)
if err != nil && !os.IsNotExist(err) { return err }
if err == nil && !fi.IsDir() { return fmt.Errorf("%s exists and is not a directory", dest) }
return nil
} Try / catch
if err := ExtractAllFromTar(r, dest); err != nil {
if strings.Contains(err.Error(), "is not a directory while resolving symlinks") {
os.RemoveAll(dest)
os.MkdirAll(dest, 0o755)
err = ExtractAllFromTar(r, dest)
}
if err != nil { return err }
} Prevention
- Always extract into a fresh or pre-cleaned directory.
- Scan tar headers for names used as both file and directory before extracting.
- Never pass a destination whose path prefix names a regular file.
- Verify destination with Lstat+IsDir before extraction.
When it happens
Trigger: ExtractAllFromTar extracting an archive containing entries like "a/b" where "a" already exists in the destination as a regular file, or where an earlier tar entry created "a" as a file and a later entry expects "a/b". Also when dest itself plus a remaining subpath crosses a file boundary.
Common situations: Extracting a tarball over a stale destination tree from a previous run (old file now blocks a directory path), archives with entries that reuse a name first as a file then as a directory, or passing a destination path whose prefix names a file (e.g. dest="out.txt/sub").
Related errors
- Failed to resolve symlinks in %#v with error: %w
- resolving base symlinks: %w
- resolving target symlinks: %w
- Unknown link target type
- Attempted path traversal in tar file: {member.name}
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/2a4e49451d39af3e.
Report an issue: GitHub.