Skip to content

Commit c74339f

Browse files
authored
fix: distinguish NOT_FOUND from DENIED in list_directory (#468)
* fix: distinguish NOT_FOUND from DENIED in list_directory When listing a nonexistent directory, the error was reported as [DENIED] which is misleading — it implies a permission issue rather than a missing path. Now ENOENT errors produce [NOT_FOUND] with a clear message, while EPERM/EACCES/ETIMEDOUT remain as [DENIED]. Updated UI parser and renderer to handle the new prefix with a distinct icon. * fix: avoid duplicate NOT_FOUND message
1 parent 9901344 commit c74339f

3 files changed

Lines changed: 16 additions & 5 deletions

File tree

src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
520520
[FILE] src/tools/filesystem.ts
521521
522522
If a directory cannot be accessed, it will show [DENIED] instead.
523+
If a path does not exist, it will show [NOT_FOUND] instead.
523524
Only works within allowed directories.
524525
525526
${PATH_GUIDANCE}

src/tools/filesystem.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,9 +686,10 @@ export async function listDirectory(dirPath: string, depth: number = 2): Promise
686686
} catch (error) {
687687
const err = error as NodeJS.ErrnoException;
688688
const displayPath = relativePath || path.basename(currentPath);
689-
// Keep [DENIED] prefix so UI parser regex still matches.
690-
// Append a hint for permission/timeout errors so user gets context.
691-
if (err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'ETIMEDOUT') {
689+
// Distinguish "not found" from "permission denied" so AI and UI get accurate info.
690+
if (err.code === 'ENOENT') {
691+
results.push(`[NOT_FOUND] ${displayPath} — path does not exist`);
692+
} else if (err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'ETIMEDOUT') {
692693
results.push(`[DENIED] ${displayPath} — not accessible (permission denied, cloud-only file, or Full Disk Access not granted)`);
693694
} else {
694695
results.push(`[DENIED] ${displayPath}`);

src/ui/file-preview/src/directory-controller.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface DirEntry {
66
name: string;
77
isDir: boolean;
88
isDenied: boolean;
9+
isNotFound: boolean;
910
isWarning: boolean;
1011
warningText: string;
1112
children: DirEntry[];
@@ -17,7 +18,7 @@ function parseDirectoryEntries(content: string): { hint: string; entries: DirEnt
1718
const hintLines: string[] = [];
1819
const entryLines: string[] = [];
1920
for (const line of lines) {
20-
if (/^\[(DIR|FILE|DENIED|WARNING)\]/.test(line.trim())) {
21+
if (/^\[(DIR|FILE|DENIED|NOT_FOUND|WARNING)\]/.test(line.trim())) {
2122
entryLines.push(line.trim());
2223
} else if (entryLines.length === 0) {
2324
hintLines.push(line);
@@ -29,6 +30,7 @@ function parseDirectoryEntries(content: string): { hint: string; entries: DirEnt
2930
fullPath: string;
3031
isDir: boolean;
3132
isDenied: boolean;
33+
isNotFound: boolean;
3234
isWarning: boolean;
3335
warningText: string;
3436
depth: number;
@@ -45,6 +47,7 @@ function parseDirectoryEntries(content: string): { hint: string; entries: DirEnt
4547
fullPath: dirName,
4648
isDir: false,
4749
isDenied: false,
50+
isNotFound: false,
4851
isWarning: true,
4952
warningText: msg,
5053
depth: parts.length,
@@ -54,13 +57,15 @@ function parseDirectoryEntries(content: string): { hint: string; entries: DirEnt
5457

5558
const isDir = line.startsWith('[DIR]');
5659
const isDenied = line.startsWith('[DENIED]');
57-
const name = line.replace(/^\[(DIR|FILE|DENIED)\]\s*/, '');
60+
const isNotFound = line.startsWith('[NOT_FOUND]');
61+
const name = line.replace(/^\[(DIR|FILE|DENIED|NOT_FOUND)\]\s*/, '');
5862
const parts = name.replace(/\\/g, '/').split('/');
5963
flat.push({
6064
name,
6165
fullPath: name,
6266
isDir,
6367
isDenied,
68+
isNotFound,
6469
isWarning: false,
6570
warningText: '',
6671
depth: parts.length - 1,
@@ -76,6 +81,7 @@ function parseDirectoryEntries(content: string): { hint: string; entries: DirEnt
7681
name: baseName,
7782
isDir: item.isDir,
7883
isDenied: item.isDenied,
84+
isNotFound: item.isNotFound,
7985
isWarning: item.isWarning,
8086
warningText: item.warningText,
8187
children: [],
@@ -116,6 +122,9 @@ function renderDirTree(entries: DirEntry[], rootPath: string): string {
116122
if (item.isDenied) {
117123
return `<div class="dir-entry"><span class="dir-icon">🚫</span> <span class="dir-name-denied">${escapeHtml(item.name)}</span></div>`;
118124
}
125+
if (item.isNotFound) {
126+
return `<div class="dir-entry"><span class="dir-icon">❓</span> <span class="dir-name-denied">${escapeHtml(item.name)}</span></div>`;
127+
}
119128
if (item.isDir) {
120129
const hasChildren = item.children.length > 0;
121130
const chevron = `<span class="dir-chevron${hasChildren ? ' expanded' : ''}">${hasChildren ? '▼' : '▶'}</span>`;

0 commit comments

Comments
 (0)