Les migrations de code, ces projets qui consistent à porter une base de code de production vers un nouveau langage, étaient des entreprises de plusieurs années jusqu'à récemment.
Le mois dernier, des développeurs individuels chez Anthropic ont migré 10 packages de code, composés de dizaines à centaines de milliers de lignes de code, en utilisant Claude Fable 5, Claude Opus 4.8, et les workflows dynamiques.
Jarred Sumner (@jarredsumner), cofondateur de Bun et membre du personnel technique chez Anthropic, a utilisé Claude Code pour migrer Bun de Zig vers Rust. Un million de lignes de code ont été produites en moins de deux semaines, avec 100 % de la suite de tests existante de Bun passant dans le CI avant la fusion. Dix-neuf régressions sont apparues après la fusion et ont toutes été corrigées. Le portage Rust a été livré dans Claude Code en juin.
Mike Krieger (@mikeyk), coresponsable d'Anthropic Labs, a migré une base de code Python vers 165 000 lignes de TypeScript en un week-end. Cela comprenait des centaines d'agents, huit portes de phase, trois cycles de révision contradictoire, et une vérification de parité finale qui a comparé la sortie de chaque commande avec l'original Python.
Les nouvelles capacités de Claude Code changent la donne pour ces projets longtemps reportés. Voici le processus en six étapes que nous utilisons désormais, tiré de ce que ces migrations nous ont appris.
L'idée centrale est que vous ne corrigez pas le code. Vous corrigez le processus (la boucle) qui a produit le code.
Pourquoi et quand migrer les langages
Les équipes lancent des migrations en raison des changements de paysage entre leur construction initiale et le projet actuel. Soit un compromis connu est devenu limitant, soit une meilleure approche a émergé, soit l'écosystème d'origine est en déclin.
Par exemple, Jarred avait initialement choisi Zig car il offrait des performances de niveau C avec une simplicité radicale, idéal pour un fondateur solo « écrivant Bun en 1 an dans un appartement exigu d'Oakland avant l'ère LLM ». Cette simplicité comportait des compromis connus, dont il parle ici.
Le CLI de Bun reçoit plus de 10 millions de téléchargements mensuels et est largement utilisé dans Claude Code.
Encore au dernier trimestre, ces compromis n'auraient pas suffi à justifier de geler la feuille de route et d'engager des ressources pour un projet de plusieurs trimestres. Vous pouviez maintenir deux bases de code parallèles pendant des trimestres ou des années, et si le résultat final était une parité à 90 %, vous aviez un mal de tête plus grand qu'au début.
Maintenant, le pire scénario est que vous supprimez la branche et réessayez.
Il doit toujours y avoir un cas d'affaire justifiable. Bien que les migrations d'un million de lignes ne coûtent plus 3 à 4 millions de dollars en ressources d'ingénierie sur un projet de quatre ans, elles coûtent encore des dizaines à des centaines de milliers de dollars ou plus à exécuter. La migration de Bun, par exemple, a consommé 5,9 milliards de tokens d'entrée non mis en cache et 690 millions de tokens de sortie — environ 165 000 $ au tarif API. La partie principale du portage de Mike était de 27 millions de tokens.

La PR d'un million de lignes de Jarred.
Cependant, le cas de migration n'a plus besoin d'être existentiel. Une année de correctifs de bugs mémoire dans le journal des modifications, ou un goulot d'étranglement chronique, peuvent désormais le justifier.
L'étape de compilation était l'impulsion du projet de Mike. L'outil interne sur lequel son équipe travaille est livré aux utilisateurs sous forme d'un binaire unique. Produire ce binaire avec la chaîne d'outils Python prenait environ huit minutes par plateforme, soit une attente totale de 30 minutes sur la matrice de build à chaque version. Après le portage, la même compilation prend maintenant environ deux secondes, le binaire démarre 6 fois plus vite, et l'équipe a pu retirer un pipeline de déploiement séparé.
Pourquoi l'IA change la donne pour les migrations de code
Fable et Opus 4.8 sont particulièrement bons pour déléguer, diriger et vérifier des flux de travail parallèles avec des sous-agents tout en trouvant plusieurs chemins vers les objectifs énoncés.
Les migrations de code de grande envergure sont un cas d'utilisation particulièrement efficace pour ces modèles avancés car :
- Le travail est parallèle. Le travail peut être exécuté sur des milliers d'unités indépendantes telles que des fichiers et des crates, donc les agents peuvent travailler en même temps plutôt que d'attendre les uns les autres.
- Le contexte est clair et complet. L'ancien code sert de bonne spécification pour le modèle.
- Il y a un arbitre intégré. De nombreuses grandes bases de code incluent une suite de tests que les agents peuvent utiliser pour vérifier leur travail.
- La file d'attente s'écrit elle-même. Lorsqu'une compilation ou un test échoue, cela devient la prochaine tâche à corriger pour un agent.
- Ils nécessitent cohérence et gestion des cas limites : les réviseurs citent la règle derrière chaque constatation, donc une violation devient un élément de file d'attente plutôt qu'une divergence silencieuse.
Six étapes pour les migrations de code de grande envergure
Pour plus de détails, vous pouvez lire le blog de Jarred.
Prérequis
Un prérequis avant de commencer votre projet de migration est d'avoir un juge solide en place, sinon vous n'aurez pas de condition de sortie ni de mesure de succès.
Pour construire ce juge :
- Catégoriser les tests existants. Utilisez Claude pour identifier quels tests sont exprimables en appels externes et lesquels dépendent d'internes qui ne seront pas portés.
- Réécrire pour la portabilité. Convertissez les tests tournés vers l'extérieur en assertions qui peuvent être exécutées à la fois contre l'original et le portage. Utilisez des agents adverses pour vérifier que les tests réécrits n'affaiblissent pas les assertions.
- Valider le juge. Exécutez-le contre le code original pour confirmer qu'il passe. Puis exécutez-le contre du code délibérément cassé pour confirmer qu'il échoue — un juge qui ne détecte pas les pannes n'est pas un juge.
Cela suit principalement la méthodologie de Jarred, avec des révisions et des portes à chaque étape. Mike a suivi une structure globale similaire en utilisant des workflows en boucle similaires, mais il a exécuté l'ensemble de la migration de bout en bout, a révisé les règles et le workflow en fonction des résultats, et l'a réexécutée — en jetant la sortie à chaque fois jusqu'à la troisième exécution.

Étape 1 — Créer le livre de règles, la carte des dépendances et l'inventaire des écarts
L'ordre compte : le livre de règles doit venir avant l'inventaire des écarts. L'inventaire des écarts est défini par ce que les valeurs par défaut du livre de règles ne couvriront pas, et les deux sont testés ensemble dans un audit conjoint.
Livre de règles
La forme exacte du livre de règles dépend des décisions architecturales clés que vous devez prendre au début. Principalement, si le nouveau code suivra la même structure, ou s'il sera complètement repensé.
S'il s'agit du premier cas (Jarred), le livre de règles sera principalement des tables de correspondance qui traduisent les types et les idiomes entre les langages tout en pointant vers l'inventaire des écarts pour les composants les plus difficiles à traduire. S'il s'agit du second cas (Mike), ce sera un document de conception.
Jarred a créé son livre de règles en discutant avec Claude, formant une politique pour chaque zone d'ambiguïté. Il a également utilisé huit sous-agents spécialement conçus pour examiner 8 catégories différentes de modes de défaillance courants basés sur sa propre intuition.
Carte des dépendances
Vous devez comprendre les dépendances entre fichiers pour diviser efficacement les flux de travail pour une migration parallèle, afin de savoir quels fichiers migrer en premier et quels fichiers contenir dans le même lot. Claude Code peut déployer des agents pour créer et exécuter un script déterministe afin de produire cette carte.
Inventaire des écarts et réviseurs sceptiques
Le nouveau langage a des exigences différentes de l'ancien langage qui doivent être satisfaites. Pour Zig vers Rust, la différence était la gestion manuelle de la mémoire (C et C++ fonctionnent de la même manière). Par exemple :
1// Zig23fn readConfig(allocator: std.mem.Allocator) ![]u8 {4 const buf = try allocator.alloc(u8, 1024);5 // ...remplir buf...6 return buf; // l'appelant doit libérer cela — mais seul le commentaire le dit7}89// Un appelant qui oublie 'defer allocator.free(buf)' compile toujours — la fuite n'apparaît qu'à l'exécution.
1fn read_config() -> Vec<u8> {2 let buf = vec![0u8; 1024];3 // ...remplir buf...4 buf // la propriété passe à l'appelant ; la mémoire est libérée automatiquement5}67// L'utiliser après l'avoir déplacé ? Le libérer deux fois ? Rien ne compile.8// Oublier de le libérer ? Il n'y a pas d'appel free à oublier — drop est automatique.
Pour Python vers TypeScript, l'écart était les interfaces et les contrats. Python n'exige pas de contrat déclarant quelle forme d'objet il acceptera ou ce qu'il retourne, mais TypeScript le fait.
Jarred et Mike ont tous deux créé des fichiers d'inventaire des écarts capturant cette connaissance implicite. Jarred a inventorié ces écarts en amont, ce que nous faisons ici, tandis que Mike a choisi de traduire d'abord puis de créer l'inventaire des écarts en auditant après. Vous devrez peut-être faire les deux.
Consultez cet exemple de prompt Claude Code pour créer un fichier d'inventaire des écarts.
Étape 2 — Tester les règles sous pression

Dans cette étape, Jarred a utilisé un agent pour traduire trois fichiers en utilisant le livre de règles, un agent pour traduire trois fichiers « comme un ingénieur Rust senior », et un agent pour utiliser le diff afin de créer de nouvelles règles de traduction. À ce stade, il a détecté deux problèmes critiques qui auraient créé de nombreux problèmes s'ils avaient été répartis sur les 1 448 fichiers.
Ce type de test de résistance ne fonctionne que pour les migrations qui préservent la structure, où deux traductions du même fichier sont comparables ligne par ligne. Si votre livre de règles est une refonte — comme celui de Mike — le test équivalent consiste à attaquer le document de conception directement avec des réviseurs adverses, puis à le valider avec une exécution de bout en bout jetable.
Quoi qu'il en soit, jetez tous les fichiers traduits. L'objectif est d'affiner les règles, pas de faire des progrès incrémentaux.
Étape 3 — Tout traduire

Pour les étapes restantes, vous exécutez la même architecture de boucle multi-agents : implémenter, réviser et corriger.
Vous pouvez déléguer le travail d'implémentation à des modèles plus petits et garder les réviseurs sur des modèles plus grands. Par exemple, Mike a utilisé Claude Sonnet lorsqu'il a déployé 12 sous-agents pour la migration principale.
La file d'attente de travail doit être mécanique. Un script batch décide ce qui est fait en vérifiant si le fichier traduit existe sur le disque, puis découpe les fichiers en attente en lots pour les agents d'implémentation. Comme la file d'attente est reconstruite à partir du disque à chaque fois, la migration est reprenable par construction.
Tout ce que le traducteur ne peut pas exécuter avec confiance est signalé avec « // TODO(port): <raison> » pour être traité à l'étape 4.
Deux réviseurs adverses évaluent le travail des implémenteurs en utilisant des contextes séparés et le désaccord entre les réviseurs va à un troisième agent. Lorsqu'un réviseur continue de détecter la même erreur à travers les fichiers, la correction n'est pas par fichier. Vous ajoutez une phrase au livre de règles et régénérez le lot affecté. Le livre de règles continue de croître à travers cette étape ; le code n'est jamais corrigé manuellement par rapport à lui.
Une décision de conception importante à noter dans cette étape est l'emplacement du compilateur. Mike a exécuté le compilateur TypeScript à l'intérieur de chaque boucle, car il vérifie une unité en quelques secondes. Jarred a banni le compilateur de la boucle entièrement et l'a reporté à l'étape suivante, car cargo prend des minutes.
Étapes 4, 5, 6 — Compiler, exécuter et faire correspondre le comportement

Ces trois étapes partagent la même architecture de boucle et nécessitent progressivement moins de jugement humain, donc nous les traitons ensemble.
Jarred a exécuté cela avec un script orchestrateur qui invoquait le compilateur une fois sur l'ensemble de l'espace de travail. Des « agents correcteurs » ont ensuite parcouru la liste d'erreurs en parallèle avec une révision adverse. La construction s'exécute à nouveau, on rince et on répète.
L'examen de la liste d'erreurs est utile pour détecter les problèmes systémiques qui peuvent nécessiter des ajustements. Par exemple, Jarred a rencontré des milliers d'erreurs de module Rust qui sont apparues après avoir corrigé les imports cycliques que la compilation paresseuse de Zig tolérait. Il a corrigé la boucle en encodant une logique pour classer quelle dépendance supprimer, déplacer ou restructurer la frontière.
L'étape 5 a également une source de vérité mécanique similaire à la liste d'erreurs du compilateur : les crashs du test de fumée. Encore une fois, la correction de la boucle consistait à regrouper les problèmes en catégories, dans ce cas en regroupant les causes par cause racine qui sont examinées par des sous-agents adverses.
L'étape 6 et la fin de notre histoire est la comparaison du comportement des programmes entre les deux bases de code.
Nos fichiers ont maintenant été traduits, compilés et testés par fumée.
Il est maintenant temps de les partitionner et d'exécuter la suite de tests (de l'étape prérequis) contre eux. Traitez les échecs avec des « agents correcteurs » qui examinent les tests échoués par rapport aux deux bases de code. Les réviseurs adverses vérifient leurs corrections.
L'étape suivante dans cette boucle est un démon de build, qui est le seul processus autorisé à reconstruire le binaire. Les correcteurs écrivent des correctifs ; le démon les regroupe, reconstruit une fois, réexécute les tests affectés et renvoie les résultats. Cela sérialise l'opération la plus coûteuse au lieu de laisser plusieurs agents la déclencher indépendamment.
L'approche de Mike est importante ici, car de nombreux développeurs n'auront pas de suite de tests développée ou portée. Mike a fait créer par Claude un petit script pour exécuter 7 scénarios réels contre le nouveau portage et la base de code Python originale, et a comparé les résultats. Chaque scénario défaillant a eu son propre agent correcteur, et la boucle a tourné jusqu'à ce que les sept réussissent.
Puis il est allé encore plus loin. Claude a conçu sa propre suite de tests de bout en bout et l'a exécutée de manière autonome pendant la nuit, corrigeant ce qui cassait et réexécutant quatre nuits de suite. En conséquence, il a attrapé les petits problèmes qu'aucune liste de scénarios n'aurait prédits.
La leçon est qu'une suite de tests manquante ne bloque pas cette étape. Si vous ne pouvez pas hériter d'un arbitre, faites-en construire un par Claude. Votre base de code originale est la vérité de base de toute façon.
Meilleures pratiques pour les migrations de code
Chaque exécution nous a appris quelque chose que la précédente n'avait pas appris. Mais quelques pratiques ont tenu dans tous les projets :
- Ne suivez pas ce guide aveuglément. Chaque migration est différente. Traitez ceci comme un point de départ et planifiez votre migration spécifique avec Claude avant de vous y engager.
- Ne vous concentrez pas sur les échecs individuels. Les échecs individuels sont le travail de la boucle. Votre attention doit porter sur les schémas.
- Rendez la révision adverse et la vérification mécanique. Laissez les scripts — un compilateur, un diff, une suite de tests — être l'arbitre.
- N'utilisez pas le plus grand modèle pour tout. Les modèles plus petits gèrent bien le déploiement à grand volume d'implémentation ; réservez votre plus grand modèle pour les réviseurs et pour tout ce qui écrit des règles que d'autres agents suivront.
- Chargez les heures humaines en amont. Le livre de règles et le test de résistance prennent le plus de temps. Tout ce qui suit consiste principalement à épuiser les files d'attente.
Examiner les résultats de la boucle, pas le code
La migration de Bun par Jarred est maintenant en production, bien que chaque migration ait des compromis. Par exemple, environ 4 % du code Rust se trouve dans des blocs « unsafe », principalement des opérations de pointeur sur une ligne aux frontières C/C++.
Mais la nouvelle base de code est mesurablement meilleure. Chaque fuite de mémoire que les outils de l'équipe peuvent détecter a été corrigée : un benchmark de 2 000 constructions répétées est passé de 6 745 Mo de mémoire à 609. Le binaire est 19 % plus petit sur Linux et Windows. Et l'optimisation cross-langage l'a rendu 2 à 5 % plus rapide sur les serveurs HTTP et les charges de travail réelles comme next build et tsc.
Choisissez la base de code que vous tolérez et demandez à Claude à quoi ressemble le processus de migration pour elle.





