Bug #17: multi_edit Misleading Success Counter
Status: RESOLVED in v3.16.0 Category: Correctness / Verification Severity: Medium-High (misleading output forces wasteful re-reads to verify) Resolution Date: 2026-03-02
Problem
Section titled “Problem”multi_edit reported ambiguous results. In real-world usage:
OK: 1/2 edits, 193 lines [backup:20260302-165154-27ead86d9eb8ef82]The message indicated that only 1 of 2 edits was applied — but reading the file confirmed both changes were present. The only way to verify was a manual read_file_range call on the edited zone, wasting tokens.
Root Cause
Section titled “Root Cause”When Edit 1’s replacement subsumes Edit 2’s intended change (overlapping edits), Edit 2’s oldText no longer exists in currentContent after Edit 1 is applied. The edit loop marked it as “failed” even though the file was already in the correct state.
Example:
- Edit 1: Replace entire function body (includes the line Edit 2 targets)
- Edit 2: Replace just one line within that function →
oldTextgone → “failed” - Result: “1/2 edits” but file is correct
Additional Issues
Section titled “Additional Issues”Comparing EditFile() vs MultiEdit(), MultiEdit was missing:
| Feature | EditFile | MultiEdit (before fix) |
|---|---|---|
| Risk assessment | CalculateChangeImpact() | Missing |
| CRITICAL blocking | ShouldBlockOperation() | Missing |
| Risk warning | FormatRiskNotice() | Missing |
| Context validation | validateEditContext() | Missing |
| Pre/Post hooks | HookPreEdit / HookPostEdit | Missing |
| Per-edit detail | N/A (single edit) | Missing |
Why It Matters
Section titled “Why It Matters”- Misleading output: “1/2 edits” implies failure when the file is correct
- Wasted tokens: Forced
read_file_rangeverification calls after every multi_edit - No risk protection: CRITICAL risk rewrites via multi_edit bypassed all safety checks
- No hooks: Custom validation logic could not intervene in batch edits
Solution
Section titled “Solution”1. “Already Present” Detection
Section titled “1. “Already Present” Detection”When performIntelligentEdit fails for an edit, check:
- Is
newTextalready present incurrentContent? - Is
oldTextabsent fromcurrentContent?
If both true → the edit was subsumed by a prior edit in the batch. Count as already_present (not failed).
2. Per-Edit Status Detail
Section titled “2. Per-Edit Status Detail”New EditDetail struct captures the outcome of each individual edit:
type EditDetailStatus string // "applied", "already_present", "failed"
type EditDetail struct { Index int Status EditDetailStatus OldTextSnippet string NewTextSnippet string MatchConfidence string Error string}3. Full Parity with EditFile
Section titled “3. Full Parity with EditFile”| Feature | Implementation |
|---|---|
| Context validation | Validates all edits against original content; only hard-blocks if NO edit passes |
| Risk assessment | Simulates all edits to compute aggregate impact on final vs original content |
| CRITICAL blocking | ShouldBlockOperation(force) — only >=90% blocks without force: true |
| Risk warning | FormatRiskNotice() for MEDIUM/HIGH appended to response |
| Pre/Post hooks | HookPreEdit before loop, HookPostEdit after write |
| Skip unnecessary write | If all edits are already_present, no file I/O occurs |
4. Improved Response Format
Section titled “4. Improved Response Format”Compact mode:
OK: 2 edits (1 applied, 1 already present), 193 lines [backup:xxx]Verbose mode:
Multi-edit completed on /path/to/fileTotal edits: 2Applied: 1Already present: 1Lines affected: 193Confidence: high
Edit details: edit 1: applied (confidence: high) edit 2: already present (subsumed by prior edit)Files Changed
Section titled “Files Changed”| File | Change |
|---|---|
core/edit_operations.go | EditDetailStatus, EditDetail types; MultiEditResult extended with SkippedEdits + EditDetails; MultiEdit() rewritten with context validation, risk assessment, hooks, already_present detection; calculateMultiEditImpact() + truncateText() helpers |
main.go | multi_edit handler response formatting for compact and verbose modes |
tests/bug17_test.go | 9 regression tests |
tests/bug16_test.go | Test content enlarged to avoid CRITICAL threshold on small files |
Testing
Section titled “Testing”- 9 new regression tests in
tests/bug17_test.go:TestBug17_OverlappingEditsNotMisreported— Edit 1 subsumes Edit 2 → SkippedEdits=1, FailedEdits=0TestBug17_AllEditsApplied— 2 independent edits → SuccessfulEdits=2TestBug17_GenuineFailureStillReported— missing oldText → FailedEdits=1TestBug17_RiskAssessmentCriticalBlocks— ~100% rewrite without force → OPERATION BLOCKEDTestBug17_RiskAssessmentCriticalForceProceeds— force=true bypasses CRITICALTestBug17_EditDetailsPopulated— 3 mixed edits → EditDetails with 3 entriesTestBug17_BackwardCompatibility— original fields (TotalEdits, SuccessfulEdits, etc.) still workTestBug17_AllAlreadyPresent— all edits already present → no write, SkippedEdits=2TestBug17_MixedOverlapAndIndependent— batch with overlap + independent edits
- Build:
go buildpasses - Full suite:
go test ./...all pass