You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Problem
Agents often continue after file edit failures ("WRITE DENIED, FILE
UNMODIFIED: old_string not found") as if nothing happened, leading to
commits with incomplete changes. The core issue is that agents send
parallel tool calls optimistically (edit + commit) before seeing
results.
## Solution
Split error communication into two channels:
1. **`error` field** (user-facing): Concise error message shown in UI
2. **`note` field** (agent-facing): Explicit failure signal and recovery
guidance
### Key Changes
- Add `note?: string` field to all file edit error results
- Introduce `EDIT_FAILED_NOTE_PREFIX` constant for consistent messaging
- Remove redundant `WRITE_DENIED_PREFIX` from error messages
- Simplify UI to detect failures via `success` field instead of string
matching
- Update tool descriptions to warn upfront that edits may fail
### Agent Notes Pattern
All notes follow this structure:
```
EDIT FAILED - file was NOT modified. [Problem]. [Fix].
```
**Examples:**
- `"EDIT FAILED - file was NOT modified. The old_string does not exist
in the file. Read the file first to get the exact current content, then
retry."`
- `"EDIT FAILED - file was NOT modified. The file has 50 lines. Read the
file to get current content, then retry."`
### Why This Approach
1. **Upfront warnings** in tool descriptions tell agents not to assume
success
2. **Clear failure signals** with "EDIT FAILED" prefix
3. **Actionable remediation** telling agents exactly what to do (read
file, retry)
4. **Follows existing pattern** - the `bash` tool already uses `note`
field successfully
### Files Modified
- `src/types/tools.ts` - Added `note` field and
`EDIT_FAILED_NOTE_PREFIX`
- `src/services/tools/file_edit_replace_shared.ts` - Added notes to all
error cases
- `src/services/tools/file_edit_operation.ts` - Pass through `note`
field
- `src/services/tools/file_edit_insert.ts` - Added notes to all error
cases
- `src/utils/tools/toolDefinitions.ts` - Updated descriptions with
failure warnings
- `src/components/tools/FileEditToolCall.tsx` - Simplified error
detection
- `src/services/tools/file_edit_replace_shared.test.ts` - New tests for
`note` field
## Testing
✅ All 127 tool tests pass (including 3 new tests)
✅ TypeScript compilation successful
✅ ESLint checks passed
_Generated with `cmux`_
@@ -53,6 +61,7 @@ export function handleStringReplace(
53
61
success: false,
54
62
error:
55
63
"old_string not found in file. The text to replace must exist exactly as written in the file.",
64
+
note: `${EDIT_FAILED_NOTE_PREFIX} The old_string does not exist in the file. ${NOTE_READ_FILE_FIRST_RETRY}`,
56
65
};
57
66
}
58
67
@@ -63,13 +72,15 @@ export function handleStringReplace(
63
72
return{
64
73
success: false,
65
74
error: `old_string appears ${occurrences} times in the file. Either expand the context to make it unique or set replace_count to ${occurrences} or -1.`,
75
+
note: `${EDIT_FAILED_NOTE_PREFIX} The old_string matched ${occurrences} locations. Add more surrounding context to make it unique, or set replace_count=${occurrences} to replace all occurrences.`,
66
76
};
67
77
}
68
78
69
79
if(replaceCount>occurrences&&replaceCount!==-1){
70
80
return{
71
81
success: false,
72
82
error: `replace_count is ${replaceCount} but old_string only appears ${occurrences} time(s) in the file.`,
83
+
note: `${EDIT_FAILED_NOTE_PREFIX} The replace_count=${replaceCount} is too high. Retry with replace_count=${occurrences} or -1.`,
73
84
};
74
85
}
75
86
@@ -123,13 +134,15 @@ export function handleLineReplace(
123
134
return{
124
135
success: false,
125
136
error: `start_line must be >= 1 (received ${args.start_line}).`,
137
+
note: `${EDIT_FAILED_NOTE_PREFIX} Line numbers must be >= 1.`,
126
138
};
127
139
}
128
140
129
141
if(args.end_line<args.start_line){
130
142
return{
131
143
success: false,
132
144
error: `end_line must be >= start_line (received start ${args.start_line}, end ${args.end_line}).`,
145
+
note: `${EDIT_FAILED_NOTE_PREFIX} The end_line must be >= start_line.`,
133
146
};
134
147
}
135
148
@@ -139,6 +152,7 @@ export function handleLineReplace(
139
152
return{
140
153
success: false,
141
154
error: `start_line ${args.start_line} exceeds current file length (${lines.length}).`,
155
+
note: `${EDIT_FAILED_NOTE_PREFIX} The file has ${lines.length} lines. ${NOTE_READ_FILE_RETRY}`,
142
156
};
143
157
}
144
158
@@ -149,6 +163,7 @@ export function handleLineReplace(
149
163
return{
150
164
success: false,
151
165
error: `expected_lines validation failed. Current lines [${currentRange.join("\n")}] differ from expected [${args.expected_lines.join("\n")}].`,
166
+
note: `${EDIT_FAILED_NOTE_PREFIX} The file content changed since you last read it. ${NOTE_READ_FILE_AGAIN_RETRY}`,
0 commit comments