{"record":{"id":"a3e02981705866a4","repo":"wavetermdev/waveterm","slug":"s-is-not-a-directory","errorCode":null,"errorMessage":"%s is not a directory","messagePattern":"(.+?) is not a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/suggestion/filewalk.go","lineNumber":162,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\treturn ch, nil\n\t}\n\n\t// Use singleflight to ensure only one listing operation occurs per key.\n\tvalue, err, _ := group.Do(key, func() (interface{}, error) {\n\t\tf, err := os.Open(dir)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer f.Close()\n\t\tfi, err := f.Stat()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !fi.IsDir() {\n\t\t\treturn nil, fmt.Errorf(\"%s is not a directory\", dir)\n\t\t}\n\t\tentries, err := f.ReadDir(maxFiles)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar results []DirEntryResult\n\t\tfor _, entry := range entries {\n\t\t\tresults = append(results, DirEntryResult{Entry: entry})\n\t\t}\n\t\t// Add parent directory (“..”) entry if not at the filesystem root.\n\t\tif filepath.Dir(dir) != dir {\n\t\t\tmockDir := &MockDirEntry{\n\t\t\t\tNameStr:  \"..\",\n\t\t\t\tIsDirVal: true,\n\t\t\t\tFileMode: fs.ModeDir | 0755,\n\t\t\t}\n\t\t\tresults = append(results, DirEntryResult{Entry: mockDir})\n\t\t}","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/suggestion/filewalk.go#L144-L180","documentation":"The directory-walk helper in pkg/suggestion verifies with f.Stat() that the path it was asked to list is actually a directory before ReadDir. If the path exists but is a regular file (or other non-dir), it returns this error instead of proceeding.","triggerScenarios":"Calling the internal listDirectory function of filewalk with a path that exists but is a file, e.g. a query resolving to /home/user/file.txt instead of a directory.","commonSituations":"File suggestion queries that autocomplete to a full file path, stale cached baseDir pointing at a file that replaced a directory, symlink resolution producing a file path.","solutions":["Check os.Stat(dir).IsDir() before invoking the directory list call","If the resolved path is a file, treat it as the matched entry rather than a directory to walk","Correct the baseDir/query resolution (resolveFileQuery) so it yields a directory"],"exampleFix":"// before\nresults, err := ListDir(candidatePath, maxFiles)\n// after\nif fi, statErr := os.Stat(candidatePath); statErr != nil || !fi.IsDir() {\n    return nil\n}\nresults, err := ListDir(candidatePath, maxFiles)","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(dir)\nif err != nil { return err }\nif !fi.IsDir() {\n    return fmt.Errorf(\"%s is not a directory\", dir)\n}","typeGuard":"func isDir(path string) bool { fi, err := os.Stat(path); return err == nil && fi.IsDir() }","tryCatchPattern":"results, err := ListDir(dir, maxFiles)\nif err != nil && strings.HasSuffix(err.Error(), \"is not a directory\") {\n    return nil // treat as no suggestions for a file path\n}","preventionTips":["os.Stat and confirm IsDir before any directory walk","Handle file-path matches as suggestion results, not walk roots","Re-validate cached/resolved baseDir paths each request since the filesystem can change"],"tags":["go","filesystem","suggestions"],"backgroundTag":"not-a-directory","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}