Found during the end-to-end review of the direct executor (#216), deliberately not fixed there — the fix changes plan_edits' public keying, which is not something to smuggle into a PR about rendering defects.
The mechanism
plan_edits keys its order list on the path as the model wrote it. So a reply containing:
a.txt
<<<<<<< SEARCH
one
=======
1
>>>>>>> REPLACE
./a.txt
<<<<<<< SEARCH
two
=======
2
>>>>>>> REPLACE
produces two entries — a.txt and ./a.txt — for one file. to_diff then renders that file's diff twice. The first hunk applies; the second is computed against the same "before" content and no longer matches the tree, so git apply refuses it.
_resolve already collapses both to the same Path, so the validation is correct: the second edit is checked against the first's pending content. Only the keying is by raw string.
Severity, honestly
Low, and it fails safely. It needs a model to write the same file two ways in one reply, which is unusual, and the outcome is a refused patch and a spent attempt — not a corrupt tree. It is filed because "unusual" is not "impossible", and because the failure message would blame the model for an edit it got right, which is the class of bug this repository has spent a day removing.
What the fix has to be careful about
plan_edits returns dict[str, tuple[str, str]] keyed by the model's path string, and apply_edits and to_diff both consume that. Keying by resolved path instead is the obvious answer, but it changes what callers see — including the order the diff is rendered in, which is currently "the order the model first named each file" and is deliberate.
Blind spots
- Nobody has checked whether a model ever actually does this. If the answer is never, the right fix might be to refuse the duplicate outright rather than merge it — a refusal the model can act on beats a silently merged edit.
- Other path spellings (
sub/../a.txt, a trailing slash, a symlinked directory inside the tree) have not been enumerated.
Found during the end-to-end review of the direct executor (#216), deliberately not fixed there — the fix changes
plan_edits' public keying, which is not something to smuggle into a PR about rendering defects.The mechanism
plan_editskeys itsorderlist on the path as the model wrote it. So a reply containing:produces two entries —
a.txtand./a.txt— for one file.to_diffthen renders that file's diff twice. The first hunk applies; the second is computed against the same "before" content and no longer matches the tree, sogit applyrefuses it._resolvealready collapses both to the samePath, so the validation is correct: the second edit is checked against the first's pending content. Only the keying is by raw string.Severity, honestly
Low, and it fails safely. It needs a model to write the same file two ways in one reply, which is unusual, and the outcome is a refused patch and a spent attempt — not a corrupt tree. It is filed because "unusual" is not "impossible", and because the failure message would blame the model for an edit it got right, which is the class of bug this repository has spent a day removing.
What the fix has to be careful about
plan_editsreturnsdict[str, tuple[str, str]]keyed by the model's path string, andapply_editsandto_diffboth consume that. Keying by resolved path instead is the obvious answer, but it changes what callers see — including the order the diff is rendered in, which is currently "the order the model first named each file" and is deliberate.Blind spots
sub/../a.txt, a trailing slash, a symlinked directory inside the tree) have not been enumerated.