최근 AI 코딩 도구들이 간단한 버그 수정 요청에도 원래 코드를 과도하게 재작성하는 '오버 에디팅(Over-Editing)' 문제가 심각하게 지적되고 있습니다. 이는 기능적으로는 정상 동작할지라도 코드 리뷰를 어렵게 만들고 코드베이스의 품질을 조용히 저하시키는 원인이 됩니다. 이를 측정하기 위해 인위적으로 버그를 주입한 데이터셋을 활용해 모델이 얼마나 불필요한 수정을 하는지 평가하는 연구가 진행되었습니다.
번역된 본문
이 글에 사용된 코드는 여기에서 확인할 수 있습니다. AI 보조 코딩은 이제 표준이 되었으며, Cursor, GitHub Copilot, Claude Code, Codex와 같은 도구를 통해 우리는 점점 더 모델이 우리의 코드를 만지도록 허용하고 있습니다. 지난 1년 동안 이러한 도구 중 하나라도 사용해 본 적이 있다면 아마 다음과 같은 상황을 겪어보셨을 것입니다. 모델에게 간단한 버그(아마도 단순한 오프 바이 원(off-by-one) 오류나 잘못된 연산자 하나)를 수정해달라고 요청합니다. 모델은 버그를 고치지만 함수의 절반이 다시 작성됩니다. 새로운 헬퍼 함수가 나타납니다. 완벽하게 합리적인 변수명이 변경됩니다. 새로운 입력 유효성 검사가 추가됩니다. 그리고 변경 사항(diff)이 엄청나게 큽니다. 저는 이를 '과도한 수정(Over-Editing)' 문제라고 부르는데, 이는 모델이 고칠 필요가 없는 코드까지 다시 작성하려는 경향을 의미합니다. 이는 보이는 것보다 더 중요한 문제입니다. 코드 리뷰는 이미 병목 현상을 겪고 있으며, 리뷰어는 무엇이 변경되었는지, 왜 변경되었는지, 그리고 그 변경이 안전한지 이해해야 합니다. 전체 함수를 다시 작성하는 모델은 결과가 올바르더라도 코드를 완전히 알아볼 수 없게 만들어 이 작업을 극적으로 어렵게 만듭니다. 이 글에서는 기존의 대규모 언어 모델(LLM)이 과도하게 수정하려는 경향이 있는지, 그리고 모델을 더 충실한 편집자로 훈련시킬 수 있는지 이 문제를 조사해 보겠습니다.
과도한 수정 (Over-Editing)
과도한 수정은 모델이 당면한 문제를 해결하기 위해 엄격하게 필요한 수준을 넘어 코드를 수정하는 것을 의미합니다. 정확히 말하자면, 모델의 출력이 기능적으로는 올바르지만 구조적으로 최소한의 수정이 요구하는 것보다 원래 코드에서 더 많이 벗어난 경우를 과도한 수정이라고 합니다. 그림 1의 예는 이를 잘 보여줍니다. 버그는 range() 호출 시 단일 오프 바이 원 오류(range(len(x) - 1)이 range(len(x))가 되어야 함)이며, 올바른 수정은 단 한 줄입니다. GPT-5.4(추론 노력이 높은 상태)는 전체 함수를 다시 작성하여 응답합니다. 명시적인 None 검사를 추가하고, dtype=float을 사용하는 np.asarray 변환을 도입하고, 유한 값 마스킹을 추가하며, 배열 크기를 확인하고, curve_fit 호출 시그니처를 변경하고, 플로팅 로직을 완전히 교체합니다. 출력은 테스트를 통과하고 기능적으로 올바르지만 변경 사항(diff)은 엄청나게 크며, 그러한 추가 사항 중 어느 것도 요청되지 않았거나 필요하지도 않았습니다. 이는 수행되는 작업의 종류 측면에서 생각해 보면 도움이 됩니다. 소프트웨어 엔지니어링은 대략 두 가지 모드로 나뉩니다: 그린필드(green-field, 처음부터 무언가를 새로 구축하는 것)와 브라운필드(brown-field, 기존 코드베이스 내에서 작업하는 것)입니다. 특히 브라운필드 환경에서 기존 코드는 팀이 이미 이해하고 있으며 의도적으로 그렇게 작성된 것입니다. 모델의 역할은 문제를 수정하는 것 외에는 없습니다. AI 코딩 도구를 사용할 때 테스트를 더 많이 작성하라는 것이 일반적인 조언입니다. 테스트가 통과하면 코드는 괜찮기 때문입니다. 그러나 과도한 수정은 브라운필드 환경에서의 실패이며, 정확성 실패와 달리 테스트 스위트에 보이지 않습니다. 모델이 더 많은 코드를 생성할수록 엔지니어가 리뷰해야 할 양이 많아지며, 과도한 수정은 이를 더 어렵게 만듭니다. 파싱해야 할 복잡한 논리가 더 많아지고, 읽어야 할 코드 줄이 늘어나며, 전체적인 코드베이스 품질이 조용히 저하될 가능성이 높아집니다.
과도한 수정 측정하기
과도한 수정을 연구하려면 먼저 '정답(ground truth)' 편집이 어느 정도의 '최소성(minimality)'을 가지고 잘 정의된 코드 편집 데이터셋이 필요합니다. 대부분의 기존 벤치마크가 하듯 또 다른 LLM을 사용하여 버그를 주입하는 대신, BigCodeBench의 400개 문제를 프로그래밍 방식으로 훼손(corrupt)시켜 더 세밀한 제어를 가능하게 했습니다. 예를 들어 비교 연산자를 뒤집거나(< → <=), +를 -로 교체하거나, 부울 값(True → False)을 변경하는 작업 등이 있습니다. 1 훼손된 각 샘플은 구문적으로 유효하며 해당 테스트 케이스를 실패하도록 검증되었습니다. 이를 통해 정답 편집이 훼손 작업을 정확히 되돌리는 것 이상은 아님을 보장하므로, 구조적으로 이 편집이 최소임을 보장합니다. 따라서 모델이 버그를 수정하는지 여부뿐만 아니라 그 과정에서 얼마나 더 많은 코드를 변경했는지 평가할 수 있습니다.
평가 지표 (Metrics)
대부분의 코딩 벤치마크는 Pass@1의 변형을 사용하여 모델의 정확성을 평가합니다. 하지만 Pass@1은 필요하지만 충분하지는 않습니다.
Code for this post is available here . AI-assisted coding has become the norm and with tools like Cursor, GitHub Copilot, Claude Code, Codex, we are increasingly letting models touch our code. If you have used any of these tools in the past year, you have probably experienced something like this: you ask the model to fix a simple bug (perhaps a single off-by-one error, or maybe a wrong operator). The model fixes the bug but half the function has been rewritten. An extra helper function has appeared. A perfectly reasonable variable name has been renamed. New input validation has been added. And the diff is enormous. I refer to this as the Over-Editing problem where models have the tendency to rewrite code that didn’t need rewriting. This matters more than it might seem. Code review is already a bottleneck and reviewers need to understand what changed, why it changed, and whether the change is safe. A model that rewrites entire functions, even correctly, makes this job dramatically harder as the code is now completely unrecognizable. In this post, I will investigate this problem: whether existing LLMs have a tendency to over-edit and whether we can train models to be more faithful editors. Over-Editing Over-editing refers to a model modifying code beyond what is strictly necessary to fix the problem at hand. To be precise: a model is over-editing if its output is functionally correct but structurally diverges from the original code more than the minimal fix requires. The example in Figure 1 illustrates this well. The bug is a single off-by-one error in a range() call ( range(len(x) - 1) should be range(len(x)) ) and the correct fix is a single line. GPT-5.4 (with high reasoning effort) responds by rewriting the entire function: it adds explicit None checks, introduces np.asarray conversions with dtype=float , adds finite-value masking, validates array sizes, changes the curve_fit call signature, and replaces the plotting logic entirely. While the output passes the tests and is functionally correct, the diff is enormous, and none of those additions were asked for or even necessary. It helps to think about this in terms of the kind of work being done. Software engineering broadly splits into two modes: green-field (building something new from scratch) and brown-field (working within an existing codebase). Specifically in brown-field, the existing code has been understood by the team and has been deliberately written the way it was. The model’s job is to fix the issue and nothing else. A common piece of advice for working with AI coding tools is to simply write more tests because if the tests pass, the code is fine. However, Over-editing is a brown-field failure where unlike correctness failures, it is invisible to test suites. As models generate more code, engineers have more to review and over-editing makes that harder. There is more complex logic to parse, more lines of code to read, and a higher chance that overall codebase quality quietly degrades. Measuring Over-Editing To study over-editing, we first need a dataset of code edits where the “ground truth” edit is well-defined with some degree of “minimality”. Rather than using another LLM to introduce bugs (which is what most existing benchmarks do), we programmatically corrupt 400 problems from BigCodeBench which gives us more fine-grained control: things like flipping a comparison operator ( < → <= ), swapping + for - , or changing boolean values ( True → False ). 1 Each corrupted sample remains syntactically valid and verified to break the corresponding test cases. This ensures that the ground truth edit is exactly the reversal of the corruption and nothing more, thus making this edit minimal by construction. We can then evaluate not just whether a model fixes the bug, but how much else it changed in the process. Metrics Most coding benchmarks evaluate models on correctness using some variant of Pass@1. However, Pass@1 is necessary but not sufficient. A model can score perfectly on Pass@1 while completely rewriting every function it touches. For this experiment, we need metrics that capture how much the model changed beyond what was required. Token-level Levenshtein Distance. Unlike standard Levenshtein which counts the minimum number of character insertions, deletions, and substitutions to transform one string into another, we use a Python token-level variant. The code is first passed through Python’s tokenizer, which splits it into its atomic syntactic units ( def , add , ( , a , , , b , ) , : , return , a , + , b ). Levenshtein is then computed over this token sequence rather than raw characters. For example, consider the following two functions: def add ( a , b ): def someotherfunctionname ( a , b ): return a + b return a + b Character-level Levenshtein gives a distance of 19. Token-level Levenshtein gives a distance of 1 since someotherfunctionname becomes a single token. We normalize by total token count so scores are comparable across functions of different lengths. In addition, rather than simply comparing the model’s output to the ground truth, we compare both against the corrupted input. Let $C$ be the corrupted solution, $G$ the ground truth, and $M$ the model’s output. The true minimal edit (simply the reversal of the corruption) is $D_{\text{true}} = d(G, C)$ and the model’s edit is $D_{\text{model}} = d(M, C)$, giving a relative patch score: $$S(M) = D_{\text{model}} - D_{\text{true}}$$ Values closer to zero indicate the model’s patch resembles the true minimal fix. The intuition is that we can interpret the original uncorrupted solution as the best possible edit to the corrupted solution, compute the scores for this best possible patch, and then compare with the model’s output. Added Cognitive Complexity. Cognitive Complexity (an improvement over Cyclomatic Complexity ) measures how hard code is to understand. It penalizes nesting, recursion, mixed logical operators, and non-obvious control flow. For example a straight line of code with no branches is much easier to read than something that requires a reader to hold state, such as an if , a loop, or try/except . An example is shown below: def process ( items ): result = [] for item in items : # +1 if item > 0 : # +2 (nesting penalty: inside a loop) if item % 2 == 0 : # +3 (nesting penalty: two levels deep) result . append ( item ) return result # Cognitive Complexity: 6 Since all our corruptions change values rather than structure, the correct fix should always add zero Cognitive Complexity. Any increase in the model’s output was introduced unprompted and is unnecessary. We report the absolute difference between the model’s output and the original, which should be zero for a faithful minimal edit. Values below 0 are also unwanted as unnecessary simplifications to code are also undesirable. Do Models Over-Edit? Yes, even frontier ones. Among the latest frontier models, GPT-5.4 over-edits the most. It has a Levenshtein of 0.39 in reasoning mode and 0.33 in non-reasoning, with Added Cognitive Complexity of 2.31 and 1.56 respectively. Despite this, its Pass@1 is only 0.723 and 0.770, making it one of the weakest correctness performers too. Claude Opus 4.6 achieves the highest Pass@1 of any model evaluated (0.912 reasoning, 0.900 non-reasoning) while also producing the smallest diffs with Levenshtein of 0.06 and 0.08, Added Cognitive Complexity of 0.20 and 0.31. Gemini 3.1 Pro Preview sits in similar territory, with GLM 5 arguably the most conservative model among the open weight ones. Does Prompting Help? Many papers that claim to uncover a new LLM failure mode do not first test whether the model can do the task when asked directly. A behavior that looks impossible in one setup may be easy under an explicit prompt, so I investigate the impact of adding “IMPORTANT: Try to preserve the original code and the logic of the original code as much as pos