file.edit: count overlapping old_text matches; refuse overlapping edits - #52
Merged
Merged
Conversation
edit_file counted old_text with str.count, which skips overlapping
matches. In a file holding "aaa", old_text="aa" with expected_count=1
passed even though "aa" occurs twice, and the edit silently took the
first one while the approval card showed it as unambiguous.
Occurrences are now found with a find(start + 1) scan (occurrences),
so "aaa"/"aa", "babab"/"bab" and an emoji run each count 2 and are
refused with COUNT_MISMATCH. When the count matches but two occurrences
share text, the edit is refused with the new OVERLAPPING message: no
single replacement can apply to both. Matches that only touch ("abab"
/ "ab") still edit.
The scan stops after EDIT_MAX_REPLACEMENTS + 1 matches. An unbounded
overlapping scan is quadratic on repetitive input (a 10 MiB "aaa..."
file with a 20k-character "aaa..." needle measured ~550 s); bounded, it
takes ~10 ms. Counts past 100 are reported as "more than 100", which
no expected_count can reach anyway.
Tests: parametrized regressions (aaa, babab, emoji run, CRLF), a later
overlapping pair, touching matches, the over-limit report, and a
Hypothesis property of occurrences against a brute-force scan. All
new tests fail against the old non-overlapping count. The app.py
handler and tool definition are unchanged, so the pinned catalog
fingerprints hold; the capabilities manifest is regenerated for the
new constant.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
edit_fileindavellm_edit.pycheckedold_textwithtext.count(needle).str.countskips overlapping matches, so in a file holdingaaa,old_text="aa"withexpected_count=1passed even thoughaaoccurs twice (at 0 and 1).text.replacethen silently edited the first one, while the approval card (approvalPreview) showed the change as if it were unambiguous.A Hypothesis property test found this while fuzzing the Notion adapter on
claude/notion-adapter-v1(#48), where the same bug was fixed indavellm_notion.py(_occurrences).Fix
occurrences(text, needle, limit)scans withfind(needle, start + 1), soaaa/aa,babab/baband🎉🎉🎉/🎉🎉each count 2 and are refused with the existingCOUNT_MISMATCHmessage.expected_count=2onaaa/aanow gets a new fixed message,OVERLAPPING: "old_text was found 2 times, but the occurrences overlap, so they cannot all be replaced; nothing was written". No single replacement can apply to two occurrences that share text. Matches that only touch (abab/ab) still edit.EDIT_MAX_REPLACEMENTS + 1(101) matches. An unbounded overlapping scan is quadratic on repetitive input: a 10 MiBaaa…file with a 20k-characteraaa…needle measured about 550 s. Bounded, it takes about 10 ms. Counts past 100 now read "more than 100", which noexpected_countcan reach anyway.str.replacefinds exactly those matches.The approval card itself is unchanged. Its "replaces N occurrences" line is now enforced when the edit runs: the edit replaces exactly N separate occurrences or writes nothing.
Constraints kept
app.pyhandler and thefile.editdefinition are untouched, sotests/fixtures/davellm/tool_catalog.json(extended_handler_code) and the definition fingerprints are unchanged.docs/DAVEHARNESS_CAPABILITIES.{json,md}were regenerated withpython scripts/generate_capabilities_manifest.pyfor the newOVERLAPPINGconstant.docs/DAVELLM_TOOLS.md(semantics plus the error table) andCLAUDE.mddescribe the overlap rule.Tests (
tests/test_file_edit.py)aaa/aa,babab/bab, an emoji run, and a CRLF file (x\nxmatches asx\r\nxtwice, overlapping on thex). Each is refused atexpected_count1 (COUNT_MISMATCH, found 2) and 2 (OVERLAPPING), with zero effects.abab/ab, count 2) still edit.aamatches (75 bystr.count) and 101amatches.occurrencesagainst a brute-forcestartswithscan. Its two-symbol alphabet makes self-overlapping needles common; a four-symbol version missed the bug in 300 examples.occurrencesis switched to non-overlapping steps (the oldstr.countsemantics). The property shrinks totext='aaa', needle='aa'.Checks (all from CLAUDE.md, run locally)
py_compile,compileall,mypy daveharness: cleanpytest -q: 764 passed, 1 skippednode --check,node --test(4/4),bash -n: cleannpm ci,npm ls,npm audit --audit-level=high: 0 vulnerabilitiesgit diff --check: cleanMerge note: conflicts with #51
#51 (
claude/tool-preflight) moves this same counting block fromedit_fileinto a shared_plan()used by both the approval preflight (check_edit) and the write. That block still usestext.count. The two PRs conflict textually indavellm_edit.pyand in the generateddocs/DAVEHARNESS_CAPABILITIES.*. Whichever lands second needs a rebase:occurrencesscan, themore than 100report, and the overlap refusal in_plan()in place offound = text.count(needle)…text.index(needle).python scripts/generate_capabilities_manifest.py.After both land, an ambiguous or overlapping edit is refused at preflight, before any approval card is shown.