Skip to content

feat(edit): show closest candidate lines on oldText mismatch (#97032)#97305

Closed
ianalloway wants to merge 3 commits into
openclaw:mainfrom
ianalloway:fix/edit-tool-mismatch-diagnostics
Closed

feat(edit): show closest candidate lines on oldText mismatch (#97032)#97305
ianalloway wants to merge 3 commits into
openclaw:mainfrom
ianalloway:fix/edit-tool-mismatch-diagnostics

Conversation

@ianalloway

@ianalloway ianalloway commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Problem

When an edit tool call fails because oldText doesn't match, the error only showed the raw file contents — no hint about why it didn't match. Agents had to re-read the file and guess whether the problem was wrong indentation, an escaped character, or a typo.

Fix

Adds findClosestLines / describeClosestLines to edit-diff.ts. On a mismatch, the error message now includes the closest matching lines with a diagnostic note (indentation mismatch, content diff, etc.).

Proof (real terminal output)

Triggering a mismatch where the file uses 4-space indent but the edit requests 8 spaces:

Could not find edits[1] in /tmp/openclaw-edit-tool-OLcy0w/demo.txt.
The oldText must match exactly including all whitespace and newlines.
Closest line(s) to your oldText (anchor: "return bar();"):
  L2: "    return bar();"  — indentation differs: expected 8 spaces, found 4 spaces
  L1: "    return foo();"  — content differs
Strings are shown JSON-escaped so whitespace and backslashes are visible.
Current file contents:
    return foo();
    return bar();

Tests

Tests  22 passed (22)   (edit.test.ts)
Tests   6 passed (6)    (edit-diff.closest-lines.test.ts)

New tests cover:

  • Indentation mismatch diagnostic (integration test)
  • Batched edit miss — diagnostic points at the correct (second) edit
  • Large-file (>500 lines) mismatch — scan is skipped, no stall risk

Closes #97032

🤖 Generated with Claude Code

When the edit tool can't match `oldText`, it now appends up to 3 of the
closest lines from the file, each with its line number and a note on the
likely cause (indentation difference, or a near-miss content difference
such as an escaped vs literal character). Strings are JSON-escaped so
whitespace and backslashes are visible.

Purely enhances the error output — the matching algorithm is unchanged
and the diagnostics only run on the existing not-found error path, so
there are no side effects on successful edits.

- add findClosestLines / describeClosestLines to edit-diff.ts (pure, tested)
- appendMismatchHint targets the specific unmatched edit's oldText
- keep the existing "Current file contents" hint
- unit tests + an integration test for the indentation case

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Copilot AI review requested due to automatic review settings June 27, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 27, 2026
@clawsweeper

clawsweeper Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: this branch is a useful candidate for edit mismatch diagnostics, but it still leaves candidate line text uncapped, while #97512 covers the same linked request with line, storage, and byte scan bounds plus proof.

Root-cause cluster
Relationship: superseded
Canonical: #97512
Summary: This PR is one of several candidate fixes for the same open edit mismatch diagnostics request; the canonical sibling is safer because it is proof-positive, mergeable, and includes bounded long-line/no-newline handling.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Canonical path: Use #97512 as the canonical bounded implementation for the linked diagnostics request, then close or supersede the overlapping candidate branches.

So I’m closing this here and keeping the remaining discussion on #97512.

Review details

Best possible solution:

Use #97512 as the canonical bounded implementation for the linked diagnostics request, then close or supersede the overlapping candidate branches.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main lacks candidate diagnostics, and PR head shows the remaining edge case where raw candidate line text is rendered without a storage/output cap.

Is this the best way to solve the issue?

No for this branch. The edit-tool mismatch path is the right owner boundary, but #97512 is the safer implementation because it includes the same feature with bounded candidate collection and no-newline regression coverage.

Security review:

Security review cleared: The diff only changes edit-tool diagnostics and tests; it does not touch dependencies, workflows, secrets, permissions, install scripts, or package resolution.

AGENTS.md: found and applied where relevant.

What I checked:

  • Repository policy read: Root AGENTS.md was read fully and applied; it requires deep PR review, current-main comparison, proof review, duplicate search, and scoped AGENTS checks for touched paths. (AGENTS.md:1, 6cb82eaab865)
  • Scoped policy read: The touched files are under src/agents, and scoped guidance was read; it affected the review by focusing on agent tool behavior and test-performance-aware verification. (src/agents/AGENTS.md:1, 6cb82eaab865)
  • Current main baseline: Current main only appends the capped Current file contents block on exact-match mismatch; it has no closest-line candidate diagnostics. (src/agents/sessions/tools/edit.ts:176, 6cb82eaab865)
  • Current PR remaining blocker: At PR head, candidates store and render the raw file line via JSON.stringify(c.text); the branch caps line count and comparison length, but not stored/rendered candidate text, so one-line minified/generated files can produce huge error output. (src/agents/sessions/tools/edit-diff.ts:423, 5559b54a8bfa)
  • Current PR test gap: The added large-file test covers files over 500 lines, but it does not cover a single long/no-newline candidate line; the negative assertion also checks a different phrase than this branch renders. (src/agents/sessions/tools/edit.test.ts:410, 5559b54a8bfa)
  • Superseding PR implementation: The sibling PR head collects mismatch candidate lines with explicit line-count, per-line storage, and total scan-byte limits before scoring and formatting. (src/agents/sessions/tools/edit.ts:261, a5aec328135e)

Likely related people:

  • mbelinky: PR fix(agents): harden edit tool recovery #52516 authored and merged the edit recovery hardening that first added current-file-content mismatch hints in the older edit-tool path. (role: introduced current mismatch-content behavior; confidence: high; commits: 922f4e66ea1a; files: src/agents/pi-tools.host-edit.ts, src/agents/pi-tools.read.ts)
  • Peter Steinberger: Commit 693d17c added edits[] payload support in the edit tool area, which is the indexed multi-edit surface affected by this diagnostics change. (role: multi-edit behavior contributor; confidence: medium; commits: 693d17c4a2ae; files: src/agents/pi-tools.host-edit.ts, src/agents/pi-tools.params.ts)
  • vincentkoc: Recent history shows direct edit-diff cleanup by Vincent Koc and merger responsibility for the no-op edit behavior PR touching the same session tool files. (role: recent adjacent contributor and merger; confidence: medium; commits: 0455028a3cd2, ab966c214be0; files: src/agents/sessions/tools/edit-diff.ts, src/agents/sessions/tools/edit.ts, src/agents/sessions/tools/edit.test.ts)
  • zw-xysk: PR fix(tools): treat no-op writes and edits as terminal tool-loop failures (fixes #96983) #97044 recently changed no-op write/edit behavior in the same edit.ts, edit-diff.ts, and edit.test.ts area. (role: recent adjacent contributor; confidence: medium; commits: ab966c214be0; files: src/agents/sessions/tools/edit.ts, src/agents/sessions/tools/edit-diff.ts, src/agents/sessions/tools/edit.test.ts)

Codex review notes: model internal, reasoning high; reviewed against 6cb82eaab865.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4b1c47268

ℹ️ 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".

Comment on lines 508 to +512
};
}
if (normalizedError.message.includes(EDIT_MISMATCH_MESSAGE)) {
throw appendMismatchHint(normalizedError, currentContent);
throw appendMismatchHint(
normalizedError,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include closest-line hints for batched edit misses

When a call has more than one real edit and one oldText is missing, applyEditsToNormalizedContent() throws the multi-edit form from getNotFoundError() (Could not find edits[i] ...), which does not contain EDIT_MISMATCH_MESSAGE. This branch is therefore skipped and the newly added closest-line diagnostic never appears for batched edit calls, even though the tool prompt encourages batching multiple edits; match that error form or use a typed error before adding the hint.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added EDIT_MISMATCH_MESSAGE_BATCH = "Could not find edits[" and extended the mismatch check to match both the single-edit and multi-edit error forms. Also changed pickUnmatchedOldText to parse the failing edit index directly from the error message (e.g. Could not find edits[1]) rather than re-doing a substring check — this correctly targets the edit that actually failed regardless of fuzzy-match semantics on earlier edits in the batch.

🤖 Addressed by Claude Code

if (lineTrimmed.length === 0) {
continue;
}
const distance = levenshtein(lineTrimmed, anchorTrimmed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound closest-line scans on large files

On any single-edit mismatch, describeClosestLines() now runs this Levenshtein calculation for every nonblank line in the full file before returning the already capped 800-byte content snippet. For large/generated files or long minified lines, a typo in oldText can spend seconds in diagnostics (the DP is capped per line but not by file size) and stall the agent instead of promptly returning the mismatch; add a file/line cap or cheap prefilter before computing distance.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added CLOSEST_LINE_FILE_SEARCH_CAP = 500 and an early return in findClosestLines when lines.length > 500. A test was added that creates a 501-line file, triggers a mismatch, and asserts the error contains Current file contents: but not Closest line(s).

🤖 Addressed by Claude Code

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. labels Jun 27, 2026
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. label Jun 27, 2026
Two follow-up fixes for the closest-line diagnostic:

1. File-size cap: `findClosestLines` now returns early when the file
   has more than 500 lines, avoiding a full O(n×m) Levenshtein walk
   on large or generated files that would stall the agent on a failed
   edit.

2. Batch-edit targeting: `pickUnmatchedOldText` previously used an
   exact-substring check that didn't match the fuzzy-match semantics
   of the edit applier. It now parses the failing edit index directly
   from the "Could not find edits[i]" error message, so the diagnostic
   always points at the edit that actually failed — not an earlier edit
   that fuzzy-matched.  The mismatch check also recognises the batched
   error form so the hint fires for multi-edit calls.

Tests added: batched-miss diagnostic and large-file scan skip.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 30, 2026
Replace global `parseInt` with `Number.parseInt` and `new Array(n)` with
`Array.from({ length: n })` to pass the unicorn lint rules.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 30, 2026
@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

edit tool: show candidate lines with diff on oldText match failure

2 participants