{"record":{"id":"a04598c7fe2b8d78","repo":"wavetermdev/waveterm","slug":"failed-to-read-directory-w","errorCode":null,"errorMessage":"failed to read directory: %w","messagePattern":"failed to read directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/util/fileutil/readdir.go","lineNumber":55,"sourceCode":"\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 {\n\t\t\t\t\tisDirMap[name] = info.IsDir()\n\t\t\t\t} else {\n\t\t\t\t\tisDirMap[name] = entry.IsDir()\n\t\t\t\t}\n\t\t\t} else {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/util/fileutil/readdir.go#L37-L73","documentation":"ReadDir wraps errors from os.ReadDir. Unlike stat, this can fail even when the path is a valid directory if the directory cannot be opened — typically a permissions problem (no read bit) or the directory was removed between stat and read. The OS cause is preserved via %w.","triggerScenarios":"os.ReadDir fails on an existing, statable directory: missing read (r) permission, directory deleted in a race, or I/O error on the underlying storage.","commonSituations":"Listing /root or another user's private directory as a normal user; container/agent sandbox without read access; directories like /proc/<pid> that vanish; NFS stale handles.","solutions":["Unwrap the cause: 'permission denied' -> chmod/chown the directory or run as a user with read access.","Re-check the directory still exists; if it races away (e.g. under /proc or a tmp build dir), add existence handling and retry.","Use `sudo -u <user> ls <dir>` to reproduce the access the library sees.","If listing is optional, catch this error and degrade gracefully instead of failing the whole operation."],"exampleFix":"// before: no error differentiation\nres, err := fileutil.ReadDir(dir, 100)\n// after\nres, err := fileutil.ReadDir(dir, 100)\nif err != nil {\n\tif os.IsPermission(errors.Unwrap(err)) {\n\t\tlog.Warnf(\"skipping unreadable dir %s\", dir)\n\t\treturn nil\n\t}\n\treturn err\n}","handlingStrategy":"try-catch","validationCode":"if f, err := os.Open(path); err != nil {\n\treturn fmt.Errorf(\"directory not readable: %w\", err)\n} else { f.Close() }","typeGuard":null,"tryCatchPattern":"res, err := fileutil.ReadDir(path, max)\nif err != nil {\n\tvar perr *fs.PathError\n\tif errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {\n\t\treturn nil // skip unreadable dir gracefully\n\t}\n\treturn err\n}","preventionTips":["Grant the process user read permission on directories it must enumerate.","Treat volatile directories (/proc, tmp build dirs) as racy: retry or skip on failure.","Test your listing flow under the actual service user, not just your dev shell."],"tags":["filesystem","go","permissions","io"],"backgroundTag":"permission-denied","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}