fix(edit): show best matching region on mismatch instead of raw file head#56857
fix(edit): show best matching region on mismatch instead of raw file head#56857imwyvern wants to merge 1 commit into
Conversation
…head When the edit tool's oldText doesn't match, the error now includes the most similar region in the file with line numbers and a similarity score, helping the model self-correct more efficiently. The fuzzy hint uses line-level sliding window comparison: - Multi-line oldText: finds the window with highest line overlap - Single-line oldText: falls back to substring search across all lines - Below 20% similarity: preserves the original file-head behavior
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41764af9d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| contentLine.trim() === oldLine.trim() || | ||
| contentLine.includes(oldLine.trim()) || | ||
| oldLine.includes(contentLine.trim()) |
There was a problem hiding this comment.
Skip empty lines when computing partial-line similarity
The partial-match branch treats blank oldText lines as a match for every content line because oldLine.trim() can be "" and contentLine.includes("") is always true. With trailing newlines or intentional blank lines in oldText, each window gets artificial score credit, so unrelated regions can exceed the 20% threshold and suppress the Current file contents fallback. This makes mismatch hints point to arbitrary locations instead of useful context.
Useful? React with 👍 / 👎.
| const snippet = contentLines | ||
| .slice(contextStart, contextEnd) | ||
| .map((l, idx) => `${contextStart + idx + 1}| ${l}`) | ||
| .join("\n"); |
There was a problem hiding this comment.
Cap best-match snippet length to prevent oversized errors
The new best-match path no longer enforces EDIT_MISMATCH_HINT_LIMIT, and the snippet size scales with oldText line count (windowSize). If oldText is large (for example, a big multi-line replacement), the thrown error can include hundreds or thousands of lines, which can bloat tool responses and exceed practical model/context limits. The fallback path still caps output to 800 chars, so this is a regression in mismatch-error size control.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR replaces the raw-file-head fallback in edit-tool mismatch errors with a Key changes:
One behavioural issue was found: for single-line Confidence Score: 4/5Safe to merge after the single-line path ordering is corrected; the change is error-message-only and cannot corrupt file content. The multi-line sliding window and snippet generation are correct. The one P2 issue — the single-line substring path always overrides the sliding window result and reports a misleading 90% score — can cause the model to be pointed at the wrong file region in certain common patterns. It does not block functionality but undermines the primary goal of showing the best matching region. src/agents/pi-tools.host-edit.ts — lines 151–166 (single-line substring early-return logic)
|
| Filename | Overview |
|---|---|
| src/agents/pi-tools.host-edit.ts | Adds findBestMatchRegion() using a sliding window + single-line substring fallback. The multi-line sliding window and the context snippet/line-numbering are correct. One issue: for single-line oldText the substring path always runs first (before the bestScore threshold), ignoring a potentially better sliding window result and reporting a hardcoded 90% score regardless of actual similarity. |
| src/agents/pi-tools.host-edit.fuzzy-hint.test.ts | Five new targeted tests covering the happy path (partial match), no-match fallback, single-line substring match, line-number formatting, and missing oldText. Tests are well-structured and exercise the new code paths cleanly. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.host-edit.ts
Line: 151-166
Comment:
**Single-line path ignores sliding window result and reports misleading score**
The single-line substring block runs unconditionally after the sliding window loop and returns early — completely discarding `bestScore`/`bestStart` — even when the window found a more similar position. Consider this scenario:
- `oldText = "baz"`
- File line 1: `"foo bar baz qux"` (contains "baz" as substring — poor match)
- File line 5: `" baz"` (trimmed exact match, sliding window score 0.5)
The substring scan hits line 1 first and returns `lineStart: 1, score: 0.9`, while the sliding window correctly ranked line 5 higher. The model is shown the wrong region with an inflated confidence percentage.
Additionally, the hardcoded `score: 0.9` bears no relationship to actual line similarity; a file that contains `"bazaar"` would be reported as "90% similar" to `oldText = "baz"` even though the line content is unrelated.
A minimal fix is to only enter the substring path when the sliding window produced a weak or no result:
```typescript
if (windowSize === 1 && bestScore < 0.5) {
const trimmedOld = oldLines[0].trim();
if (trimmedOld.length > 0) {
for (let i = 0; i < contentLines.length; i++) {
if (contentLines[i].includes(trimmedOld)) {
return {
lineStart: i + 1,
snippet: contentLines
.slice(Math.max(0, i - 1), i + 2)
.map((l, idx) => `${Math.max(1, i) + idx}| ${l}`)
.join("\n"),
score: 0.9,
};
}
}
}
}
```
This lets an exact-trimmed window match (score 0.5) take priority over a mere substring hit anywhere in the file.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(edit): show best matching region on ..." | Re-trigger Greptile
| if (windowSize === 1) { | ||
| const trimmedOld = oldLines[0].trim(); | ||
| if (trimmedOld.length > 0) { | ||
| for (let i = 0; i < contentLines.length; i++) { | ||
| if (contentLines[i].includes(trimmedOld)) { | ||
| return { | ||
| lineStart: i + 1, | ||
| snippet: contentLines | ||
| .slice(Math.max(0, i - 1), i + 2) | ||
| .map((l, idx) => `${Math.max(1, i) + idx}| ${l}`) | ||
| .join("\n"), | ||
| score: 0.9, | ||
| }; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Single-line path ignores sliding window result and reports misleading score
The single-line substring block runs unconditionally after the sliding window loop and returns early — completely discarding bestScore/bestStart — even when the window found a more similar position. Consider this scenario:
oldText = "baz"- File line 1:
"foo bar baz qux"(contains "baz" as substring — poor match) - File line 5:
" baz"(trimmed exact match, sliding window score 0.5)
The substring scan hits line 1 first and returns lineStart: 1, score: 0.9, while the sliding window correctly ranked line 5 higher. The model is shown the wrong region with an inflated confidence percentage.
Additionally, the hardcoded score: 0.9 bears no relationship to actual line similarity; a file that contains "bazaar" would be reported as "90% similar" to oldText = "baz" even though the line content is unrelated.
A minimal fix is to only enter the substring path when the sliding window produced a weak or no result:
if (windowSize === 1 && bestScore < 0.5) {
const trimmedOld = oldLines[0].trim();
if (trimmedOld.length > 0) {
for (let i = 0; i < contentLines.length; i++) {
if (contentLines[i].includes(trimmedOld)) {
return {
lineStart: i + 1,
snippet: contentLines
.slice(Math.max(0, i - 1), i + 2)
.map((l, idx) => `${Math.max(1, i) + idx}| ${l}`)
.join("\n"),
score: 0.9,
};
}
}
}
}This lets an exact-trimmed window match (score 0.5) take priority over a mere substring hit anywhere in the file.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.host-edit.ts
Line: 151-166
Comment:
**Single-line path ignores sliding window result and reports misleading score**
The single-line substring block runs unconditionally after the sliding window loop and returns early — completely discarding `bestScore`/`bestStart` — even when the window found a more similar position. Consider this scenario:
- `oldText = "baz"`
- File line 1: `"foo bar baz qux"` (contains "baz" as substring — poor match)
- File line 5: `" baz"` (trimmed exact match, sliding window score 0.5)
The substring scan hits line 1 first and returns `lineStart: 1, score: 0.9`, while the sliding window correctly ranked line 5 higher. The model is shown the wrong region with an inflated confidence percentage.
Additionally, the hardcoded `score: 0.9` bears no relationship to actual line similarity; a file that contains `"bazaar"` would be reported as "90% similar" to `oldText = "baz"` even though the line content is unrelated.
A minimal fix is to only enter the substring path when the sliding window produced a weak or no result:
```typescript
if (windowSize === 1 && bestScore < 0.5) {
const trimmedOld = oldLines[0].trim();
if (trimmedOld.length > 0) {
for (let i = 0; i < contentLines.length; i++) {
if (contentLines[i].includes(trimmedOld)) {
return {
lineStart: i + 1,
snippet: contentLines
.slice(Math.max(0, i - 1), i + 2)
.map((l, idx) => `${Math.max(1, i) + idx}| ${l}`)
.join("\n"),
score: 0.9,
};
}
}
}
}
```
This lets an exact-trimmed window match (score 0.5) take priority over a mere substring hit anywhere in the file.
How can I resolve this? If you propose a fix, please make it concise.|
Closing this PR because the author has more than 10 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit. |
…head When edit oldText doesn't match exactly, the error hint now locates and displays the most similar region in the file (with line numbers) instead of dumping the first 800 characters. This helps the model quickly spot which lines diverged from its expectation. Falls back to the current full-file-head behavior when no region scores above zero shared lines. Combines the intent of openclaw#56857 and openclaw#42265.
Problem
When the edit tool's
oldTextdoesn't match, the error currently shows the first 800 characters of the file. For large files where the intended edit target is deep in the content, this is unhelpful — the model has to re-read the file and guess where the mismatch occurred.Solution
Replace the raw file-head fallback with a best-match region finder that uses line-level sliding window comparison:
Example error output (before)
Example error output (after)
Changes
src/agents/pi-tools.host-edit.ts: AddedfindBestMatchRegion()and updatedappendMismatchHint()to passoldTextand use region matchingsrc/agents/pi-tools.host-edit.fuzzy-hint.test.ts: 5 new tests covering partial match, fallback, single-line match, line numbers, and missing oldTextTesting
All 5 new tests pass. Existing edit tool tests unaffected — this only enhances error messages on mismatch.