{"record":{"id":"2a4e49451d39af3e","repo":"kovidgoyal/kitty","slug":"s-is-not-a-directory-while-resolving-symlinks-in","errorCode":null,"errorMessage":"%s is not a directory while resolving symlinks in %s","messagePattern":"(.+?) is not a directory while resolving symlinks in (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tools/utils/tar.go","lineNumber":110,"sourceCode":"\n\t\tdest += path[start:end]\n\n\t\t// Resolve symlink.\n\n\t\tfi, err := os.Lstat(dest)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tif end < len(path) {\n\t\t\t\t\tdest += path[end:]\n\t\t\t\t}\n\t\t\t\treturn filepath.Clean(dest), nil\n\t\t\t}\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif fi.Mode()&fs.ModeSymlink == 0 {\n\t\t\tif !fi.Mode().IsDir() && end < len(path) {\n\t\t\t\treturn \"\", fmt.Errorf(\"%s is not a directory while resolving symlinks in %s\", dest, path)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Found symlink.\n\n\t\tlinksWalked++\n\t\tif linksWalked > 255 {\n\t\t\treturn \"\", fmt.Errorf(\"EvalSymlinksThatExist: too many symlinks in %s\", path)\n\t\t}\n\n\t\tlink, err := os.Readlink(dest)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif isWindowsDot && !filepath.IsAbs(link) {\n\t\t\t// On Windows, if \".\" is a relative symlink,","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/tools/utils/tar.go#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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\").","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."],"exampleFix":"// before\nerr := ExtractAllFromTar(tarReader, \"build/out\") // build/out is an old regular file\n// → \"build/out is not a directory while resolving symlinks in build/out/pkg/file.go\"\n\n// after\nos.RemoveAll(\"build/out\")\nos.MkdirAll(\"build/out\", 0o755)\nerr := ExtractAllFromTar(tarReader, \"build/out\")","handlingStrategy":"validation","validationCode":"func destIsCleanDir(dest string) bool {\n\tfi, err := os.Lstat(dest)\n\tif os.IsNotExist(err) { return true }\n\treturn err == nil && fi.IsDir()\n}","typeGuard":"func extractionPathClear(dest string) error {\n\tfi, err := os.Lstat(dest)\n\tif err != nil && !os.IsNotExist(err) { return err }\n\tif err == nil && !fi.IsDir() { return fmt.Errorf(\"%s exists and is not a directory\", dest) }\n\treturn nil\n}","tryCatchPattern":"if err := ExtractAllFromTar(r, dest); err != nil {\n\tif strings.Contains(err.Error(), \"is not a directory while resolving symlinks\") {\n\t\tos.RemoveAll(dest)\n\t\tos.MkdirAll(dest, 0o755)\n\t\terr = ExtractAllFromTar(r, dest)\n\t}\n\tif err != nil { return err }\n}","preventionTips":["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."],"tags":["tar","extraction","filesystem","symlink","path-resolution"],"backgroundTag":"path-component-not-a-directory","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}