{"record":{"id":"2f09bfbdcac7f01c","repo":"wavetermdev/waveterm","slug":"path-is-not-a-directory","errorCode":null,"errorMessage":"path is not a directory","messagePattern":"path is not a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/util/fileutil/readdir.go","lineNumber":50,"sourceCode":"\tEntries      []DirEntryOut `json:\"entries\"`\n\tEntryCount   int           `json:\"entry_count\"`\n\tTotalEntries int           `json:\"total_entries\"`\n\tTruncated    bool          `json:\"truncated,omitempty\"`\n}\n\nfunc ReadDir(path string, maxEntries int) (*ReadDirResult, error) {\n\texpandedPath, err := wavebase.ExpandHomeDir(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to expand path: %w\", err)\n\t}\n\n\tfileInfo, err := os.Stat(expandedPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat path: %w\", err)\n\t}\n\n\tif !fileInfo.IsDir() {\n\t\treturn nil, fmt.Errorf(\"path is not a directory\")\n\t}\n\n\tentries, err := os.ReadDir(expandedPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read directory: %w\", err)\n\t}\n\n\ttotalEntries := len(entries)\n\n\tisDirMap := make(map[string]bool)\n\tsymlinkCount := 0\n\tfor _, entry := range entries {\n\t\tname := entry.Name()\n\t\tif entry.Type()&fs.ModeSymlink != 0 {\n\t\t\tif symlinkCount < 1000 {\n\t\t\t\tsymlinkCount++\n\t\t\t\tfullPath := filepath.Join(expandedPath, name)\n\t\t\t\tif info, err := os.Stat(fullPath); err == nil {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/util/fileutil/readdir.go#L32-L68","documentation":"ReadDir lists directory contents, so after stat succeeds it asserts fileInfo.IsDir(). If the expanded path is a regular file, symlink-to-file, device, etc., this plain error is returned with no path embedded. The message is intentionally terse; the caller is expected to have passed a directory.","triggerScenarios":"Calling ReadDir with a file path instead of a directory path — e.g. ReadDir(\"/etc/passwd\", 100) or ReadDir(\"main.go\", ...).","commonSituations":"A variable holds a file path where a directory was expected; shell-style completion handed back a file; a symlink resolves to a file; code that assumed a config value is always a folder.","solutions":["Pass the containing directory instead: strip the filename from the path.","Stat the path in the caller and branch: use ReadDir for directories and a file-read API for files.","If the target is a symlink to a directory it works fine; only symlink-to-file triggers this — resolve the real target to confirm.","Add UI/logic validation that the user-selected path is a directory before invoking ReadDir."],"exampleFix":"// before\nfileutil.ReadDir(\"/etc/passwd\", 100)\n// after\nfileutil.ReadDir(\"/etc\", 100)","handlingStrategy":"validation","validationCode":"info, err := os.Stat(path)\nif err != nil { return err }\nif !info.IsDir() {\n\treturn fmt.Errorf(\"%s is a file; pass its parent directory\", path)\n}","typeGuard":"func isDir(path string) bool {\n\tinfo, err := os.Stat(path)\n\treturn err == nil && info.IsDir()\n}","tryCatchPattern":"res, err := fileutil.ReadDir(path, max)\nif err != nil && err.Error() == \"path is not a directory\" {\n\t// switch to a file-read API or use filepath.Dir(path)\n}","preventionTips":["Stat and branch before calling: directory -> ReadDir, file -> file-read API.","In file pickers, restrict selection to directories for directory APIs.","Strip filenames with filepath.Dir when only the parent is needed."],"tags":["filesystem","go","path","validation"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}