wagoodman/dive · error
unable to read symlink %q: %s
Error message
unable to read symlink %q: %s
What it means
Raised as a panic by NewFileInfoFromPath (dive/filetree/file_info.go:58) while hashing a directory tree. When a walked entry is a symlink (info.Mode()&os.ModeSymlink != 0), dive calls os.Readlink(realPath); if the OS refuses, the library panics with 'unable to read symlink %q: %s'. There is no error return path - the whole process unwinds.
Source
Thrown at dive/filetree/file_info.go:58
Gid: header.Gid,
IsDir: header.FileInfo().IsDir(),
}
}
func NewFileInfo(realPath, path string, info os.FileInfo) FileInfo {
var err error
// todo: don't use tar types here, create our own...
var fileType byte
var linkName string
var size int64
if info.Mode()&os.ModeSymlink != 0 {
fileType = tar.TypeSymlink
linkName, err = os.Readlink(realPath)
if err != nil {
panic(fmt.Errorf("unable to read symlink %q: %s", realPath, err))
}
} else if info.IsDir() {
fileType = tar.TypeDir
} else {
fileType = tar.TypeReg
size = info.Size()
}
var hash uint64
if fileType != tar.TypeDir {
file, err := os.Open(realPath)
if err != nil {
panic(fmt.Errorf("unable to open file %q: %s", realPath, err))
}
defer file.Close()
hash = getHashFromReader(file)
}View on GitHub (pinned to d6c691947f)
Solutions
- Re-run the analysis on a quiescent directory: stop the build/watcher that mutates symlinks while the walk runs
- Verify you can read the offending link yourself: readlink <path> using the same user dive runs as; fix ownership or run with adequate privileges
- Exclude the problematic subtree (symlink farms like /proc or vendored caches) from the scan
- As a library consumer, pre-filter symlinks before delegating to NewFileInfoFromPath (see validationCode)
Example fix
// before: passing every walked entry straight in
info, _ := d.Info()
fi := filetree.NewFileInfoFromPath(path, path, info) // panics on unreadable symlink
// after: probe the link first and skip entries you cannot resolve
info, _ := d.Info()
if info.Mode()&os.ModeSymlink != 0 {
if _, err := os.Readlink(path); err != nil {
log.Printf("skipping unreadable symlink %s: %v", path, err)
return nil // skip, don't crash
}
}
fi := filetree.NewFileInfoFromPath(path, path, info) Defensive patterns
Strategy: validation
Validate before calling
// before calling NewFileInfoFromPath on a walked entry:
func readableSymlink(realPath string, info os.FileInfo) bool {
if info.Mode()&os.ModeSymlink == 0 {
return true
}
_, err := os.Readlink(realPath)
return err == nil
}
if !readableSymlink(realPath, info) {
return nil // skip entry, avoid the panic
}
fi := filetree.NewFileInfoFromPath(path, realPath, info) Try / catch
// Go has no catch; contain the panic at the walk boundary if you must keep going:
func safeFileInfo(path, real string, info os.FileInfo) (fi filetree.FileInfo, ok bool) {
defer func() {
if r := recover(); r != nil {
log.Printf("dive panicked on %s: %v", real, r)
ok = false
}
}()
return filetree.NewFileInfoFromPath(path, real, info), true
} Prevention
- Scan static snapshots: copy (cp -a/rsync) volatile directories before analysis
- Run dive as a user that can read every symlink target in the tree (or prune what it cannot)
- Exclude symlink-heavy pseudo-filesystems and caches from the walk
When it happens
Trigger: Calling NewFileInfoFromPath on a filesystem walk where a symlink target disappears between lstat and readlink (TOCTOU), a symlink the effective user cannot read, or platform quirks such as Windows junctions/symlink reparse points that os.Readlink cannot parse.
Common situations: Running dive's directory-analysis on a build workspace that is being modified concurrently (a build deleting symlinks mid-walk), scanning root-owned or container-created symlinks as a non-root user, or scanning trees containing special filesystem links (/proc, node_modules on Windows mounts).
Related errors
- unable to open file %q: %s
- cannot open export file: %w
- failed to read CI config file %s: %w
- unable to read file: %w
- cannot write to export file: %w
AI-assisted analysis of wagoodman/dive@d6c691947f (2026-08-15).
Data as JSON: /api/errors/d0d9f3b8b3f90c91.
Report an issue: GitHub.