Skip to content

fix(runtime): keep apply_patch insertion-only hunks at the end of the file - #175

Open
kevin9327 wants to merge 1 commit into
deer-flow:mainfrom
kevin9327:fix/apply-patch-hunk-order
Open

kevin9327 wants to merge 1 commit into
deer-flow:mainfrom
kevin9327:fix/apply-patch-hunk-order

Conversation

@kevin9327

Copy link
Copy Markdown

Summary

When the built-in apply_patch tool receives an update whose insertion-only hunk (@@ followed only by + lines) comes before a hunk that adds or removes lines, the inserted lines are written into the middle of the file instead of at its end, and the tool still reports Success. For example, inserting omega and then replacing beta with two lines in alpha / beta / gamma / delta produces ... gamma / omega / delta instead of ... delta / omega.

An insertion-only hunk records the original end of the file as its position. _applyHunks then applied the collected replacements in reverse patch order, so a later hunk higher in the file was spliced first and shifted the end-of-file position the insertion had recorded. Codex sorts replacements by position before applying them bottom-up (codex-rs/apply-patch/src/file_update.rs: replacements.sort_by_key(|(index, _, _)| *index)); this does the same. Hunks that are already in file order are applied exactly as before.

This is independent of #174. Both touch apply-patch.ts in different functions, and the two branches merge cleanly.

Validation

bun test packages/runtime/tests/tools/built-in/apply-patch.test.ts

Before the fix (new test added, source unchanged): 7 pass, 1 fail.

packages\runtime\tests\tools\built-in\apply-patch.test.ts:
176 |     await fs.writeFile(filePath, original, "utf8");
177 |     await applyPatch(patch(edit, insertion), workspace);
178 |     expect(await fs.readFile(filePath, "utf8")).toBe(expected);
179 | 
180 |     await fs.writeFile(filePath, original, "utf8");
181 |     await applyPatch(patch(insertion, edit), workspace);
                                                            ^
error: expect(received).toBe(expected)

@@ -4,5 +4,5 @@
  gamma
- delta
  omega
+ delta
  "

- Expected  - 1
+ Received  + 1

      at <anonymous> (packages\runtime\tests\tools\built-in\apply-patch.test.ts:181:55)
(fail) apply_patch built-in > keeps insertion-only hunks at the end when a later hunk grows the file [18.15ms]

 7 pass
 1 fail
 16 expect() calls
Ran 8 tests across 1 file. [530.00ms]

After the fix: 8 pass, 0 fail.

  • The first half of the new test (the same two hunks with the edit first) passes both before and after, as do the 7 existing tests, including appends insertion-only hunks and normalizes a final newline. This shows that only the out-of-order case changes.
  • bun test packages/runtime/tests/tools/built-in/built-in-tools-module.test.ts: 5 pass / 1 fail on both main and this branch. The failure (runs bash commands from the workspace root) is pre-existing locally because that test spawns bash.
  • ESLint (--max-warnings 0) on both touched files: clean on main and on this branch.
  • tsc --noEmit -p packages/runtime/tsconfig.json: clean on main and on this branch.
  • Prettier: both files already have unformatted lines on main (16 and 6); this change adds none.

🤖 Generated with Claude Code

… file

An insertion-only hunk records the original end of the file as its
position, while later hunks can change how many lines precede it.
Replacements were applied in patch order reversed rather than by
position, so when such a hunk came before an edit that added or removed
lines, its lines were written into the middle of the file and the tool
still reported success. Sort replacements by position before applying
them bottom-up, as Codex does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant