Skip to content

Commit 06e886b

Browse files
daniel-lxsroomote
authored andcommitted
fix: implement graceful file not found error handling with unified display
- Show styled FileNotFoundError component in UI for better user experience - Display all missing files in a single component instead of multiple - Component supports both single and multiple file paths - Keep error message in tool result XML for AI model awareness - Prevent duplicate 'Error reading file' message by not calling handleError - Improved error detection to catch various file not found scenarios - Updated component styling to match diff_error appearance - Added translations for plural forms in all 17 supported languages
1 parent d5c66f6 commit 06e886b

File tree

21 files changed

+169
-93
lines changed

21 files changed

+169
-93
lines changed

src/core/tools/readFileTool.ts

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ export async function readFileTool(
198198
lineRanges: entry.lineRanges,
199199
}))
200200

201+
// Track files that were not found
202+
const notFoundFiles: string[] = []
203+
201204
// Function to update file result status
202205
const updateFileResult = (path: string, updates: Partial<FileResult>) => {
203206
const index = fileResults.findIndex((result) => result.path === path)
@@ -621,31 +624,38 @@ export async function readFileTool(
621624
errorMsg.includes("File not found"))
622625

623626
if (isFileNotFound) {
624-
// Emit a proper typed message for file not found errors
625-
await cline.say(
626-
"file_not_found_error",
627-
JSON.stringify({
628-
filePath: relPath,
629-
error: "The requested file does not exist in the current workspace.",
630-
}),
631-
)
627+
// Track the missing file
628+
notFoundFiles.push(relPath)
632629

633630
updateFileResult(relPath, {
634631
status: "error",
635632
error: "File not found",
636-
xmlContent: "", // Empty to prevent duplicate error display
633+
xmlContent: `<file><path>${relPath}</path><error>File not found: The requested file does not exist in the current workspace.</error></file>`,
637634
})
635+
// Don't call handleError for file not found - we'll emit a unified message later
638636
} else {
639637
updateFileResult(relPath, {
640638
status: "error",
641639
error: errorMsg,
642640
xmlContent: `<file><path>${relPath}</path><error>${errorMsg}</error></file>`,
643641
})
642+
// Only call handleError for non-file-not-found errors
643+
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
644644
}
645-
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
646645
}
647646
}
648647

648+
// Emit a unified file not found error message if any files were not found
649+
if (notFoundFiles.length > 0) {
650+
await cline.say(
651+
"file_not_found_error",
652+
JSON.stringify({
653+
filePaths: notFoundFiles,
654+
error: "The requested files do not exist in the current workspace.",
655+
}),
656+
)
657+
}
658+
649659
// Generate final XML result from all file results
650660
const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)
651661
const filesXml = `<files>\n${xmlResults.join("\n")}\n</files>`
@@ -731,23 +741,18 @@ export async function readFileTool(
731741
errorMsg.includes("File not found"))
732742

733743
if (isFileNotFound) {
734-
// Emit a proper typed message for file not found errors
735-
await cline.say(
736-
"file_not_found_error",
737-
JSON.stringify({
738-
filePath: relPath,
739-
error: "The requested file does not exist in the current workspace.",
740-
}),
741-
)
744+
// Track the missing file
745+
notFoundFiles.push(relPath)
742746

743747
// If we have file results, update the first one with the error
744748
if (fileResults.length > 0) {
745749
updateFileResult(relPath, {
746750
status: "error",
747751
error: "File not found",
748-
xmlContent: "", // Empty to prevent duplicate error display
752+
xmlContent: `<file><path>${relPath}</path><error>File not found: The requested file does not exist in the current workspace.</error></file>`,
749753
})
750754
}
755+
// Don't call handleError for file not found - we'll emit a unified message later
751756
} else {
752757
// If we have file results, update the first one with the error
753758
if (fileResults.length > 0) {
@@ -757,9 +762,20 @@ export async function readFileTool(
757762
xmlContent: `<file><path>${relPath}</path><error>${errorMsg}</error></file>`,
758763
})
759764
}
765+
// Only call handleError for non-file-not-found errors
766+
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
760767
}
761768

762-
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
769+
// Emit a unified file not found error message if any files were not found
770+
if (notFoundFiles.length > 0) {
771+
await cline.say(
772+
"file_not_found_error",
773+
JSON.stringify({
774+
filePaths: notFoundFiles,
775+
error: "The requested files do not exist in the current workspace.",
776+
}),
777+
)
778+
}
763779

764780
// Generate final XML result from all file results
765781
const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,10 @@ export const ChatRowContent = ({
10601060
</div>
10611061
)
10621062
case "file_not_found_error":
1063-
const errorData = safeJsonParse<{ filePath: string; error: string }>(message.text || "{}")
1064-
return <FileNotFoundError filePath={errorData?.filePath || ""} />
1063+
const errorData = safeJsonParse<{ filePath?: string; filePaths?: string[]; error: string }>(
1064+
message.text || "{}",
1065+
)
1066+
return <FileNotFoundError filePaths={errorData?.filePaths || errorData?.filePath || ""} />
10651067
case "user_feedback":
10661068
return (
10671069
<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">

webview-ui/src/components/chat/FileNotFoundError.tsx

Lines changed: 56 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,69 @@ import React from "react"
22
import { useTranslation } from "react-i18next"
33

44
interface FileNotFoundErrorProps {
5-
filePath: string
6-
isExpanded?: boolean
7-
onToggleExpand?: () => void
5+
filePaths: string | string[]
86
}
97

10-
export const FileNotFoundError: React.FC<FileNotFoundErrorProps> = ({
11-
filePath,
12-
isExpanded = false,
13-
onToggleExpand,
14-
}) => {
8+
export const FileNotFoundError: React.FC<FileNotFoundErrorProps> = ({ filePaths }) => {
159
const { t } = useTranslation()
16-
17-
const headerStyle: React.CSSProperties = {
18-
display: "flex",
19-
alignItems: "center",
20-
gap: "10px",
21-
marginBottom: "10px",
22-
wordBreak: "break-word",
23-
cursor: onToggleExpand ? "pointer" : "default",
24-
userSelect: "none",
25-
}
26-
27-
const containerStyle: React.CSSProperties = {
28-
backgroundColor: "var(--vscode-inputValidation-warningBackground)",
29-
border: "1px solid var(--vscode-inputValidation-warningBorder)",
30-
borderRadius: "4px",
31-
padding: "12px",
32-
marginTop: "8px",
33-
marginBottom: "8px",
34-
}
35-
36-
const iconStyle: React.CSSProperties = {
37-
color: "var(--vscode-editorWarning-foreground)",
38-
fontSize: "16px",
39-
marginBottom: "-1.5px",
40-
}
41-
42-
const titleStyle: React.CSSProperties = {
43-
color: "var(--vscode-editorWarning-foreground)",
44-
fontWeight: "bold",
45-
}
46-
47-
const pathStyle: React.CSSProperties = {
48-
fontFamily: "var(--vscode-editor-font-family)",
49-
fontSize: "var(--vscode-editor-font-size)",
50-
marginTop: "8px",
51-
marginBottom: "4px",
52-
wordBreak: "break-all",
53-
}
54-
55-
const messageStyle: React.CSSProperties = {
56-
color: "var(--vscode-foreground)",
57-
opacity: 0.9,
58-
fontSize: "var(--vscode-font-size)",
59-
}
10+
const paths = Array.isArray(filePaths) ? filePaths : [filePaths]
11+
const isMultiple = paths.length > 1
6012

6113
return (
62-
<div style={containerStyle}>
63-
<div style={headerStyle} onClick={onToggleExpand}>
64-
<span className="codicon codicon-warning" style={iconStyle} />
65-
<span style={titleStyle}>{t("chat:fileOperations.fileNotFound")}</span>
66-
{onToggleExpand && (
67-
<div style={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }}>
68-
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`} />
69-
</div>
70-
)}
14+
<div
15+
style={{
16+
marginTop: "8px",
17+
marginBottom: "8px",
18+
}}>
19+
<div
20+
style={{
21+
display: "flex",
22+
alignItems: "center",
23+
gap: "10px",
24+
marginBottom: "8px",
25+
fontSize: "var(--vscode-font-size)",
26+
color: "var(--vscode-editor-foreground)",
27+
}}>
28+
<span
29+
className="codicon codicon-warning"
30+
style={{
31+
color: "var(--vscode-editorWarning-foreground)",
32+
opacity: 0.8,
33+
fontSize: 16,
34+
marginBottom: "-1.5px",
35+
}}
36+
/>
37+
<span style={{ fontWeight: "bold" }}>
38+
{isMultiple ? t("chat:fileOperations.filesNotFound") : t("chat:fileOperations.fileNotFound")}
39+
</span>
7140
</div>
72-
{(isExpanded || !onToggleExpand) && (
73-
<>
74-
<div style={pathStyle}>
75-
<code>{filePath}</code>
41+
<div
42+
style={{
43+
paddingLeft: "26px", // Align with text after icon (16px icon + 10px gap)
44+
}}>
45+
{paths.map((path, index) => (
46+
<div
47+
key={index}
48+
style={{
49+
fontFamily: "var(--vscode-editor-font-family)",
50+
fontSize: "var(--vscode-editor-font-size)",
51+
marginBottom: index === paths.length - 1 ? "8px" : "4px",
52+
wordBreak: "break-all",
53+
color: "var(--vscode-foreground)",
54+
}}>
55+
<code>{path}</code>
7656
</div>
77-
<div style={messageStyle}>{t("chat:fileOperations.fileNotFoundMessage")}</div>
78-
</>
79-
)}
57+
))}
58+
<div
59+
style={{
60+
color: "var(--vscode-descriptionForeground)",
61+
fontSize: "var(--vscode-font-size)",
62+
}}>
63+
{isMultiple
64+
? t("chat:fileOperations.filesNotFoundMessage")
65+
: t("chat:fileOperations.fileNotFoundMessage")}
66+
</div>
67+
</div>
8068
</div>
8169
)
8270
}

webview-ui/src/i18n/locales/ca/chat.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/chat.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@
189189
"wantsToInsertWithLineNumber": "Roo wants to insert content into this file at line {{lineNumber}}:",
190190
"wantsToInsertAtEnd": "Roo wants to append content to the end of this file:",
191191
"fileNotFound": "File Not Found",
192-
"fileNotFoundMessage": "The requested file does not exist in the current workspace."
192+
"filesNotFound": "Files Not Found",
193+
"fileNotFoundMessage": "The requested file does not exist in the current workspace.",
194+
"filesNotFoundMessage": "The requested files do not exist in the current workspace."
193195
},
194196
"directoryOperations": {
195197
"wantsToViewTopLevel": "Roo wants to view the top level files in this directory:",

webview-ui/src/i18n/locales/es/chat.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/fr/chat.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/hi/chat.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/id/chat.json

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)