{"record":{"id":"afb6b38aabd62851","repo":"charmbracelet/crush","slug":"error-listing-directory-w","errorCode":null,"errorMessage":"error listing directory: %w","messagePattern":"error listing directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/ls.go","lineNumber":152,"sourceCode":"\t\t},\n\t)\n}\n\nfunc ListDirectoryTree(searchPath string, params LSParams, lsConfig config.ToolLs) (string, LSResponseMetadata, error) {\n\tif _, err := os.Stat(searchPath); os.IsNotExist(err) {\n\t\treturn \"\", LSResponseMetadata{}, fmt.Errorf(\"path does not exist: %s\", searchPath)\n\t}\n\n\tdepth, limit := lsConfig.Limits()\n\tmaxFiles := cmp.Or(limit, maxLSFiles)\n\tfiles, truncated, err := fsext.ListDirectory(\n\t\tsearchPath,\n\t\tparams.Ignore,\n\t\tcmp.Or(params.Depth, depth),\n\t\tmaxFiles,\n\t)\n\tif err != nil {\n\t\treturn \"\", LSResponseMetadata{}, fmt.Errorf(\"error listing directory: %w\", err)\n\t}\n\n\tmetadata := LSResponseMetadata{\n\t\tNumberOfFiles: len(files),\n\t\tTruncated:     truncated,\n\t}\n\ttree := createFileTree(files, searchPath)\n\n\tvar output string\n\tif truncated {\n\t\toutput = fmt.Sprintf(\"There are more than %d files in the directory. Use a more specific path or use the Glob tool to find specific files. The first %[1]d files and directories are included below.\\n\", maxFiles)\n\t}\n\tif depth > 0 {\n\t\toutput = fmt.Sprintf(\"The directory tree is shown up to a depth of %d. Use a higher depth and a specific path to see more levels.\\n\", cmp.Or(params.Depth, depth))\n\t}\n\treturn output + \"\\n\" + printTree(tree, searchPath), metadata, nil\n}\n","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/ls.go#L134-L170","documentation":"After the existence check, ListDirectoryTree delegates to fsext.ListDirectory. Any walk/read error from that call is wrapped as 'error listing directory', preserving the underlying cause (permission denied, symlink loops, I/O errors).","triggerScenarios":"Calling ListDirectoryTree on an existing path where fsext.ListDirectory fails, e.g. the process lacks read permission, the path is a file not a directory in edge cases, or the walker hits an unreadable subdirectory treated as fatal.","commonSituations":"Directories with restrictive modes (0700 owned by another user), encrypted/mounted volumes that return I/O errors, or sandboxed environments blocking traversal.","solutions":["Inspect the wrapped %w error to find the root cause (permission vs I/O).","Fix filesystem permissions on the target directory (chmod/chown) or run as an appropriate user.","Confirm the path is a readable directory, not a special file.","Retry if the failure was transient (e.g. network mount)."],"exampleFix":"// before\ntree, _, _ := ListDirectoryTree(\"/root/secret\", params, cfg)\n// after\n// chmod +rx the directory or choose a readable path first","handlingStrategy":"try-catch","validationCode":"if fi, err := os.Stat(searchPath); err == nil && !fi.IsDir() {\n    return errors.New(\"not a directory\")\n}","typeGuard":"null","tryCatchPattern":"tree, meta, err := ListDirectoryTree(p, params, cfg)\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) { /* handle permission/I/O root cause */ }\n}","preventionTips":["Ensure read/execute bits on directories being listed","Watch for transient errors on network mounts and retry","Check the wrapped error with errors.As for fs.PathError"],"tags":["filesystem","permissions","directory"],"backgroundTag":"directory-listing-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}