Warum Agents Große Refactorings Verpatzen (und Wie Man den Scope Eingrenzt, Damit das Nicht Passiert)
Ich bat Claude Code, einen zentralen Domänentyp in meiner gesamten Anwendung umzubenennen — Account in Workspace, plus drei daran hängende Felder. Vielleicht sechzig Dateien. Es startete schnell: Das erste Dutzend war sauber, Imports aktualisiert, Call Sites umbenannt, Typen durchgezogen. Ich ließ es laufen.
Nach etwa dreißig Dateien begann es abzudriften. Es fing an, Account in Dateien erneut umzubenennen, die es zwanzig Minuten zuvor bereits bearbeitet hatte, weil diese Änderungen zu diesem Zeitpunkt aus dem Kontext gefallen waren. In zwei Dateien wurde die Änderung nur halb angewendet: Der Typ war Workspace, aber eine Methode gab noch die alte Form zurück. Der Typecheck, den ich am Ende durchführte, hatte neunzig Fehler, einige in Code, den der Agent bereits „fertig" gestellt hatte.
Nichts davon war die Schuld eines dummen Modells. Ich hatte einem einzigen Agent eine Aufgabe übertragen, die größer war als er in seinem Kopf behalten konnte, ohne Möglichkeit, sich unterwegs selbst zu überprüfen — das agentische Äquivalent, einem Junior-Entwickler zu sagen „geh und benne das überall um" und für eine Stunde wegzugehen.
Die Kurzfassung: Agents scheitern bei großen Refactorings aus einem strukturellen Grund, nicht aus einem Intelligenzproblem — die Änderung ist größer als der Kontext, auf den sie verlässlich achten können, und nichts zwingt sie zur Verifizierung, bevor sie abdriften. Man behebt das durch Scoping: erst planen, die Arbeit in unabhängig verifizierbare Schritte aufteilen, zwischen jedem einen deterministischen Check durchführen und eine schriftliche Checkliste führen, die der Agent erneut liest, statt sie zu erinnern. Wo die Änderung mechanisch ist, Agent überspringen und einen Codemod verwenden.
Warum die Räder Abgehen
Drei Dinge kumulieren sich bei einem großen Refactoring, und alle haben mit dem Kontext zu tun.
Das Fenster füllt sich, die Aufmerksamkeit verkommt. Anthropics eigenes Best-Practices-Dokument ist direkt dazu: „Claudes Kontextfenster füllt sich schnell, und die Performance nimmt ab, wenn es sich füllt ... Claude kann anfangen, frühere Anweisungen zu 'vergessen' oder mehr Fehler zu machen" (Claude Code best practices). Das ist nicht einzigartig für Claude. Der „Lost in the Middle"-Effekt — wo Modelle Informationen, die in der Mitte eines langen Kontexts liegen, am wenigsten beachten — ist gut dokumentiert, und Forschungen zeigen, dass Degradierung lange vor dem Füllen des Fensters beginnen kann. Während ein Refactoring läuft, stapelt sich jedes gelesene File und jede Befehlsausgabe im selben endlichen Fenster und drängt Ihre ursprünglichen Anweisungen und die erklärten Invarianten in die aufmerksamkeitsschwache Mitte.
Das effektive Fenster ist kleiner als das Etikett. Cursor-Benutzer stoßen ständig darauf: Ein Modell mit einem beworbenen 200K-Fenster hat oft nur ~40–60K nutzbare Tokens, sobald der eigene Overhead des Harness, der System-Prompt und Tool-Ausgaben eingerechnet werden, und „beim dritten oder vierten Schritt arbeitet der Agent mit degradiertem Bewusstsein darüber, was er in Schritt eins getan hat" (Morph, Augment Code on Cursor multi-file refactors). Ein Refactoring, das sich über 50 Dateien mit 200K Tokens Quelltext erstreckt, passt schlicht nicht, also werden die Dateien 31–50 von einem Agent bearbeitet, der Dateien 1–30 bereits vergessen hat.
Dateiübergreifende Verträge sind für einen Datei-für-Datei-Editor unsichtbar. Das gesamte Risiko eines Refactorings lebt in den Nähten: Der Caller, der die alte Rückgabeform angenommen hat, das Fixture, das um den alten Feldnamen gebaut wurde, der Serializer, der noch den alten Key schreibt. Der Agent bearbeitet Datei A korrekt und Datei B korrekt, aber der Vertrag zwischen ihnen ist etwas, das keine einzelne Datei ausdrückt. Dort leben halb angewendete Änderungen und gebrochene Invarianten.
Für ein sauberes Rename wie meines ist die erste ehrliche Frage, ob ein Agent das überhaupt anfassen sollte.
Codemod Verwenden, Wenn die Änderung Mechanisch Ist
Wenn die Transformation strukturell und regelbasiert ist — ein Symbol umbenennen, eine Call-Signatur ändern, einen Import austauschen — schlägt ein deterministischer AST-Codemod einen Agent auf jeder wichtigen Achse. Tools wie jscodeshift operieren auf dem abstrakten Syntaxbaum, wenden also exakt dieselbe Transformation auf jeden übereinstimmenden Knoten an, parallel, über eine gesamte Codebase „in Sekunden", ohne Chance, dass zwei Dateien auseinanderdriften (Toptal, Martin Fowler on codemods). Ein Agent dagegen „hat auch eine Chance zur Halluzination, gibt jedes Mal leicht unterschiedliche Ergebnisse und ist langsam und kostspielig" bei einer großen Codebase (Carlos Cuesta).
Der Schachzug, der die Stärke des Agents wirklich nutzt: ihn den Codemod schreiben lassen, nicht die Bearbeitung durchführen. Man beschreibt die Transformation auf Deutsch, er generiert die jscodeshift-Transformation, man testet sie an drei Dateien und führt sie dann deterministisch auf allen sechzig aus. Man erhält die Gewandtheit des Agents beim Ausdrücken der Regel und die Garantie des AST-Tools, dass die Regel überall identisch angewendet wird.
# Agent schreibt transforms/account-to-workspace.js, du führst es aus:
npx jscodeshift -t transforms/account-to-workspace.js src/ --dry # Vorschau
npx jscodeshift -t transforms/account-to-workspace.js src/ # anwenden
git diff --stat # 60 Dateien, eine deterministische Transformation
Mein Rename war zu 80 % mechanisch. Der richtige Aufruf war ein Codemod für die Umbenennungen und ein Agent nur für die ~12 Dateien, die Urteilsvermögen erforderten — den Serializer, die Migration, die Stellen, wo Account und Workspace während einer Übergangsphase koexistieren mussten. Zum Agent greifen, wo die Änderung das Lesen von Absicht erfordert, nicht wo sie das Anwenden einer Regel erfordert.
Erst Planen, in einem Modus, der Nicht Bearbeiten Kann
Wenn die Änderung wirklich einen Agent braucht, lassen Sie ihn nicht in Runde eins mit der Bearbeitung beginnen. Der größte Hebel ist die Trennung von Erkundung und Planung von der Ausführung, sodass der Agent sich auf einen Ansatz festlegt, bevor er die Festplatte berührt.
In Claude Code ist das der Planmodus (Shift+Tab, oder --permission-mode plan): Der Agent liest Dateien, verfolgt Imports und erstellt einen Plan, ohne Bearbeitungen vorzunehmen, und Sie genehmigen ihn zuerst (common workflows). Aider hat die entsprechende Aufteilung — /architect zum Planen der Umstrukturierung, dann /code zum Ausführen (refactoring a large codebase with aider). Der Punkt ist nicht die Zeremonie. Einen Agent „direkt ins Codieren springen zu lassen, kann Code erzeugen, der das falsche Problem löst", und ein Plan, den man in zwei Minuten liest, entdeckt das, bevor eine Stunde Bearbeitungen kostet (best practices).
Das Dokument zieht eine klare Linie, wann es sich lohnt: „Wenn man den Diff in einem Satz beschreiben kann, Plan überspringen." Ein Multi-File-Refactoring passt nie in einen Satz. Plant man es.
Ein nützliches Detail für große Aufgaben: Den ersten Plan nicht akzeptieren, der sich in „alle 60 Dateien bearbeiten" zerlegt. Darauf bestehen, in Abhängigkeitsreihenfolge zu refaktorieren — mit den Modulen beginnen, von denen nichts anderes abhängt, und nach außen arbeiten — und die Arbeit in Phasen aufzuteilen, die klein genug sind, um verifiziert zu werden. In der Praxis bleiben Agents bei etwa 20–40 Dateien in einem Durchgang zuverlässig; darüber hinaus lautet der Konsens, in Phasen aufzuteilen und sie als separate Sitzungen mit einem Commit zwischen jeder zu betreiben (Claude Code multi-file refactoring walkthrough).
In Unabhängig Verifizierbare Schritte Zerschneiden, mit Einem Gate Dazwischen
Das ist der Teil, der einen wirklich rettet. Ein Refactoring, das der Agent in einem undifferenzierten Durchgang macht, hat keine internen Checkpoints, also korrumpiert der erste Fehler stillschweigend alles danach. Ein in Schritte zerschnittenes Refactoring — jeder endet in einem Check, der besteht oder fehlschlägt — kann einen Fehler nicht über das Gate hinaus ausbreiten, das ihn auffängt.
Anthropic rahmt die gesamte Disziplin darum: „Claude hört auf, wenn die Arbeit fertig aussieht. Ohne einen Check, den es ausführen kann, ist 'sieht fertig aus' das einzige verfügbare Signal, und man wird zur Verifikationsschleife." Einen Check geben, der pass/fail zurückgibt — eine Test-Suite, einen Build-Exit-Code, einen Typecheck — „und die Schleife schließt sich von selbst" (best practices). Der Check wandelt „der Agent denkt, es ist fertig" in „der Typechecker stimmt zu, dass es fertig ist" um.
Hier ist der gestaffelte Plan, den ich für das Account → Workspace-Rename hätte verwenden sollen, in Abhängigkeitsreihenfolge, mit einem Gate nach jedem Schritt:
PLAN: Account -> Workspace umbenennen. Nach jedem Schritt, der das Gate besteht, committen.
[ ] 1. Typ + Felder in src/domain/ umbenennen (keine Abhängigen).
GATE: tsc --noEmit sauber in src/domain/. Committen.
[ ] 2. Datenschicht (repositories, serializers) aktualisieren, die den Typ importiert.
GATE: tsc --noEmit sauber; Unit-Tests für src/data/ bestanden. Committen.
[ ] 3. Call Sites der Service/API-Schicht aktualisieren.
GATE: tsc --noEmit sauber im gesamten Repo; Integrationstests bestanden. Committen.
[ ] 4. Test-Fixtures + Factories auf neue Feldnamen aktualisieren.
GATE: Vollständige Test-Suite grün. Committen.
[ ] 5. UI-Komponenten + verbleibende String-Referenzen aktualisieren.
GATE: tsc --noEmit sauber; Build erfolgreich; E2E-Smoke bestanden. Committen.
Jeder Schritt ist klein genug, um in den Kontext zu passen, endet in einem deterministischen Gate und wird ge-committet, bevor der nächste beginnt. Wenn Schritt 3 neunzig Fehler wirft, sieht man sie bei Schritt 3 — mit Schritten 1 und 2 bereits sicher in git — nicht am Ende über alle sechzig Dateien. Das Commit ist genauso wichtig wie das Gate: Ein verpatzer Schritt ist ein git reset entfernt, kein Archäologieprojekt.
Dem Agent sagen, das Gate selbst auszuführen und die Ausgabe zu zeigen, nicht Erfolg zu behaupten. „Claude dazu bringen, Beweise zu zeigen, statt Erfolg zu behaupten: die Testausgabe, den Befehl, den es ausgeführt hat, und was zurückgekommen ist" (best practices). Zur strengen Durchsetzung kann ein Stop-Hook den Check als Skript ausführen und sich weigern, den Zug zu beenden, bis er besteht.
Die Checkliste in Einer Datei Halten, die der Agent Nicht Verlieren Kann
Der obige Plan hilft nur, wenn der Agent ihn in Schritt 5 noch hat. Das wird er nicht, wenn er nur in der Konversation lebt — bis Schritt 5 sind die frühen Schritte aus dem Fenster gealtert, was genau das Scheitern ist, mit dem wir begonnen haben. Also kommt die Checkliste in eine Datei: PLAN.md oder REFACTOR.md im Repo, mit der Anweisung an den Agent, es zu lesen, den nächsten unbehakten Schritt zu tun, das Gate auszuführen und den Kasten abzuhaken.
Anthropic empfiehlt genau das für lange, erschöpfende Aufgaben: Für „Code-Migrationen, das Beheben zahlreicher Lint-Fehler oder das Ausführen komplexer Build-Skripte" Claude dazu bringen, „eine Markdown-Datei (oder sogar ein GitHub-Issue!) als Checkliste und Arbeitsnotizblock zu verwenden" (best practices). Eine Datei im Repo ist externes Gedächtnis. Der Agent liest sie jeden Zug erneut, sodass der Plan bei voller Aufmerksamkeit bleibt, statt in der Mitte eines aufgeblähten Kontexts zu zerfallen. Die abgehakten Kästen sind der dauerhafte Nachweis dessen, was tatsächlich erledigt ist — nicht was der Agent vage erinnert, getan zu haben.
Damit Kontext-Hygiene paaren. Zwischen Schritten — oder zwischen Phasen, die als separate Sitzungen ausgeführt werden — /clear, damit der nächste Schritt sauber beginnt, nur mit der Checkliste und den Dateien des aktuellen Schritts geladen. Jede Erkundung „geh lesen, wie X funktioniert" an einen Subagenten delegieren, damit das Lesen in einem separaten Fenster stattfindet und nur die Zusammenfassung zurückkommt. (Beide haben hier ihre eigenen Beiträge — ich werde sie nicht erneut darlegen.)
Ein Verifizierungsdurchgang, der den Code Nicht Geschrieben Hat
Ein letztes Gate, nachdem die Schritte erledigt sind: Den gesamten Diff mit einem frischen Agent überprüfen, der die Implementierung nie gesehen hat. Ein Prüfer, „der in einem frischen Subagenten-Kontext läuft, sieht nur den Diff und die Kriterien, die man ihm gibt, nicht die Überlegung, die die Änderung hervorgebracht hat, also bewertet er das Ergebnis zu seinen eigenen Bedingungen" (best practices). Auf den Plan zeigen:
Subagenten verwenden, um den vollständigen Diff gegen REFACTOR.md zu überprüfen.
Prüfen, dass die Änderung jedes Schritts vollständig angewendet ist — keine Datei
mit dem alten Typ oder alten Feldnamen, keine Methode, die noch die alte Form zurückgibt.
Nur Lücken melden, die Korrektheit oder eine genannte Anforderung brechen, nicht Stil.
Dieser letzte Satz zählt. Ein Prüfer, dem gesagt wird, Lücken zu finden, wird sie erfinden — „zusätzliche Abstraktionsschichten, defensiven Code und Tests für Fälle, die nicht eintreten können" — also den Scope auf Korrektheit und Vertrag beschränken, sonst tauscht man ein sauberes Refactoring gegen eine Woche Gold-Plating (best practices).
Was ich immer wieder neu lerne: Der Agent ist nicht der Flaschenhals bei einem großen Refactoring — der Scope ist es. Ein 60-Datei-Rename, der en bloc übergeben wird, driftet, wird halb angewendet und lügt darüber, fertig zu sein, egal wie gut das Modell ist. Derselbe Rename, in fünf abhängigkeitsgeordnete Schritte geschnitten, jeder durch einen Typecheck gesichert, jeder ge-committet, in einer Datei verfolgt, die der Agent erneut liest — der gelingt, weil ich ihn zu keinem Zeitpunkt bat, mehr zu halten, als er konnte. Planen, aufschneiden, absichern, aufschreiben. Und wenn die Änderung nur eine Regel ist, Codemod schreiben und den AST den Teil erledigen lassen, bei dem Agents am schwächsten sind.
