# Ce que Claude Code a écrit de notre infrastructure, et ce que nous avons refusé de fusionner

Nous utilisons Claude Code quotidiennement pour le travail d'infrastructure, sur une plateforme que nous exploitons pour un client américain : stacks CDK, scripts de déploiement, politiques IAM, alarmes, runbooks. Environ un tiers du code d'infrastructure commité ces six derniers mois a d'abord été rédigé par lui. Voici un bilan honnête : ce qu'il a écrit et que nous avons fusionné tel quel, ce qu'il a écrit et que nous avons corrigé, et les quatre propositions que nous avons refusées, avec les raisons, parce que les refus sont plus instructifs que les réussites.

## La forme du travail

Les changements d'infrastructure sur cette plateforme passent par la même revue de pull request que le code applicatif, et la production de l'agent y passe aussi. Il tourne dans un bac à sable avec un accès en lecture au dépôt et un rôle AWS restreint qui peut faire `cdk diff` contre le staging et lire CloudWatch, mais ne peut pas déployer. Chaque changement qu'il propose est une branche et une PR. Un humain lit le diff, lit la sortie de `cdk diff` que l'agent colle dans la PR, et fusionne ou non. Les déploiements sont [le même script que toujours](/fr/blog/same-tag-deploy-and-the-deploy-script-without-ci), lancé par un humain.

Cette configuration est la raison pour laquelle le reste de cet article est calme. L'agent peut proposer n'importe quoi ; il ne peut rien exécuter. Chacun des refus ci-dessous a été attrapé à la PR.

<div class="article-figure">
<svg viewBox="0 0 900 220" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pipeline des changements d'infrastructure rédigés par l'agent. Claude Code dans un bac à sable avec accès en lecture au dépôt et un rôle AWS en lecture seule pour cdk diff et CloudWatch. Il produit une branche et une pull request avec la sortie du diff collée dedans. Un humain examine et fusionne ou refuse. Un humain lance le script de déploiement. L'agent n'a de permission de déploiement à aucune étape.">
<defs><marker id="arrA" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4fffb0"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<rect x="20" y="40" width="200" height="120" rx="12" fill="#151b2e" stroke="#7b8cff" stroke-width="1.5"/><text x="120" y="64" text-anchor="middle" fill="#7b8cff" font-weight="700">Claude Code · bac à sable</text><text x="120" y="90" text-anchor="middle" fill="#f1f3ff">dépôt : lecture + branche</text><text x="120" y="108" text-anchor="middle" fill="#f1f3ff">AWS : cdk diff, lecture CloudWatch</text><text x="120" y="136" text-anchor="middle" fill="#ff6b8a" font-size="11">pas de déploiement · pas d'écriture AWS</text>
<line x1="222" y1="100" x2="258" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="260" y="40" width="200" height="120" rx="12" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="360" y="64" text-anchor="middle" fill="#ffd166" font-weight="700">pull request</text><text x="360" y="90" text-anchor="middle" fill="#f1f3ff">le changement de code</text><text x="360" y="108" text-anchor="middle" fill="#f1f3ff">sortie de cdk diff collée</text><text x="360" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">le raisonnement dans la description</text>
<line x1="462" y1="100" x2="498" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="500" y="40" width="180" height="120" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="590" y="64" text-anchor="middle" fill="#4fffb0" font-weight="700">revue humaine</text><text x="590" y="90" text-anchor="middle" fill="#f1f3ff">fusionner · corriger · refuser</text><text x="590" y="108" text-anchor="middle" fill="#f1f3ff">même exigence que le code applicatif</text><text x="590" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">les quatre refus attrapés ici</text>
<line x1="682" y1="100" x2="718" y2="100" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrA)"/>
<rect x="720" y="40" width="160" height="120" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="800" y="64" text-anchor="middle" fill="#4fffb0" font-weight="700">déploiement humain</text><text x="800" y="90" text-anchor="middle" fill="#f1f3ff">le script habituel</text><text x="800" y="108" text-anchor="middle" fill="#f1f3ff">staging, puis prod</text><text x="800" y="136" text-anchor="middle" fill="#9aa3c7" font-size="11">l'agent n'y participe pas</text>
<text x="450" y="200" text-anchor="middle" fill="#9aa3c7">L'agent peut tout proposer et rien exécuter. C'est ce qui rend le reste de cet article calme.</text>
</g>
</svg>
</div>

## Fusionné tel quel

**Alarmes et tableaux de bord CloudWatch.** La meilleure catégorie. Face à « alarme sur la DLQ email, le taux de 5xx App Runner et Aurora ACU au maximum pendant 10 minutes », il a produit du CDK correct avec des seuils raisonnables, les bonnes statistiques (p99 et non la moyenne pour la latence), `treatMissingData` réglé comme il faut, et un topic SNS branché sur le canal d'astreinte existant. Les alarmes sont fastidieuses, bien documentées et ont une réponse juste évidente ; c'est le point idéal.

**Le script de [l'exercice de restauration](/fr/blog/backups-you-never-restored-are-not-backups).** Le script qui réapplique PITR, TTL, streams et tags à une table DynamoDB restaurée, en lisant les réglages voulus dans les définitions CDK. Environ 120 lignes de TypeScript avec le SDK AWS, correctes dès la première PR, y compris la pagination que les humains oublient.

**Les runbooks.** Face à l'extrait de log d'un incident et au correctif appliqué, il écrit une page de runbook claire au format maison. Ses runbooks sont meilleurs que les nôtres, parce qu'il ne saute pas les étapes qu'il considère comme évidentes.

**La politique de confiance du [rôle de déploiement OIDC](/fr/blog/github-oidc-deploy-roles-per-aws-account).** Condition `sub` correcte, audience correcte, limitée au dépôt et à la branche. Il a aussi ajouté, sans qu'on le demande, un commentaire expliquant pourquoi la revendication `sub` doit être comparée avec `StringLike` pour le motif de branche, ce qui est précisément ce que les gens se trompent à faire.

**Propagation des tags de coût.** Un changement transversal ajoutant les tags `CostCenter` et `Owner` à chaque stack via un aspect. Mécanique, 40 fichiers touchés, zéro erreur.

## Fusionné après corrections

**Le stack de [points de terminaison VPC](/fr/blog/nat-gateway-the-most-expensive-line-you-do-not-see).** Il a ajouté les points de terminaison correctement mais a mis celui de S3 en point de terminaison d'interface plutôt qu'en passerelle, ce qui fonctionne mais coûte 7 $ par mois pour quelque chose de gratuit. Attrapé en lisant le `cdk diff`. Le genre d'erreur qu'un humain qui ne l'a jamais fait ferait aussi.

**Un changement de concurrence Lambda.** Il a fixé une concurrence réservée sur une fonction pour protéger une API en aval, ce qui était la demande, mais a choisi le nombre en lisant la limite de débit documentée de l'aval et en la divisant par la durée moyenne d'invocation. L'arithmétique était juste ; l'hypothèse que les invocations sont uniformément réparties ne l'était pas. Nous l'avons divisé par deux. Le raisonnement était dans la description de la PR, ce qui a fait de la correction une conversation d'une minute.

**Resserrement des groupes de sécurité.** Prié de réduire l'entrée du groupe de sécurité Aurora au seul connecteur VPC App Runner et aux Lambdas, il l'a fait, et a aussi retiré la règle pour l'hôte bastion que le runbook référence encore pour l'accès d'urgence. Le bastion est éteint 99 % du temps, donc `cdk diff` en staging n'a rien montré de cassé. Un humain qui connaissait le bastion a remis la règle. L'agent ne pouvait pas savoir, parce que le rôle du bastion était dans la tête de quelqu'un, pas dans le dépôt. Il est dans le dépôt maintenant.

## Refusé

**1. « Ajoutez `cdk deploy` au rôle de l'agent pour que je puisse vérifier les changements de bout en bout. »** Proposé dans une description de PR comme amélioration d'efficacité, avec un argumentaire solide : diff n'est pas déploiement, certaines erreurs n'apparaissent qu'au déploiement, le staging est isolé. Tout est vrai. Refusé, parce que tout le modèle repose sur le fait que l'agent ne peut pas changer l'état AWS, et « juste le staging » est la façon dont ça s'érode. Le staging partage une base de code CDK avec la production ; un bug de déploiement en staging est un aperçu d'un bug en production, et nous voulons qu'un humain voie l'aperçu.

**2. Un `RemovalPolicy.DESTROY` sur le cluster Aurora de staging « pour accélérer le démontage ».** Correct isolément ; le staging est jetable. Refusé parce que le même construct est instancié pour la production avec un drapeau d'environnement, et que [nous avons déjà perdu des ressources à cause d'exactement cette forme de changement](/fr/blog/cloudformation-deleted-our-app-runner-services). La règle dans cette base de code est que les politiques de suppression sont `RETAIN` partout et que le démontage est une opération manuelle, nommée. L'agent ne pouvait pas connaître l'histoire ; le CLAUDE.md la dit maintenant.

**3. Une instruction IAM avec joker pour « débloquer l'indexeur de recherche ».** L'indexeur échouait avec un accès refusé sur un nouvel ARN de stream DynamoDB. Le correctif proposé était `dynamodb:*` sur `*`, avec un commentaire « à resserrer plus tard ». Refusé, évidemment, et la partie intéressante est que, quand on lui a demandé le correctif minimal à la place, il a produit l'ARN exact du stream et les quatre actions dont le consommateur a besoin en moins d'une minute. Le joker n'était pas une limite de capacité ; c'était le chemin de moindre résistance, et la revue est ce qui a fait prendre l'autre chemin.

**4. Déplacer des secrets de Secrets Manager vers SSM pour économiser 8 $ par mois.** Une vraie économie, correctement calculée, et [un argument que nous avons nous-mêmes avancé](/fr/blog/secrets-in-cdk-secrets-manager-ssm-or-nothing-in-the-template). Refusé pour les deux secrets en question parce qu'ils sont tournés par la Lambda de rotation de Secrets Manager, que SSM n'a pas, et que la PR ne tenait pas compte de la rotation. La moitié a été fusionnée : les trois secrets statiques ont bougé, les deux tournés sont restés.

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bilan sur six mois. Fusionné tel quel : alarmes et tableaux de bord, script de l'exercice de restauration, runbooks, politique de confiance OIDC, propagation des tags. Fusionné après corrections : type de point de terminaison VPC, nombre de concurrence Lambda, groupe de sécurité qui a retiré la règle du bastion. Refusé : permission de déploiement pour l'agent, politique de suppression DESTROY sur un construct partagé, IAM joker pour débloquer, secrets tournés vers SSM. Motif : le travail mécanique et bien documenté fusionne proprement ; tout ce qui dépend de l'histoire ou du rayon d'impact a besoin de l'humain.">
<g font-family="Inter,system-ui,sans-serif" font-size="11">
<rect x="20" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/><text x="155" y="44" text-anchor="middle" fill="#4fffb0" font-weight="700">fusionné tel quel</text>
<g fill="#f1f3ff"><text x="40" y="72">alarmes et tableaux de bord</text><text x="40" y="92">script de réglages de restauration</text><text x="40" y="112">runbooks depuis les logs d'incident</text><text x="40" y="132">politique de confiance OIDC, commentée</text><text x="40" y="152">tags de coût sur 40 fichiers</text></g>
<text x="155" y="190" text-anchor="middle" fill="#9aa3c7">fastidieux · documenté · une réponse juste</text><text x="155" y="208" text-anchor="middle" fill="#9aa3c7">le point idéal</text>
<rect x="315" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/><text x="450" y="44" text-anchor="middle" fill="#ffd166" font-weight="700">fusionné après corrections</text>
<g fill="#f1f3ff"><text x="335" y="72">point S3 : interface, pas passerelle</text><text x="335" y="92">concurrence : bon calcul, mauvais modèle</text><text x="335" y="112">groupe de sécurité : bastion retiré</text></g>
<text x="450" y="150" text-anchor="middle" fill="#9aa3c7">chaque correction a été une conversation d'une minute</text><text x="450" y="168" text-anchor="middle" fill="#9aa3c7">parce que le raisonnement était dans la PR</text><text x="450" y="200" text-anchor="middle" fill="#9aa3c7">le bastion vivait dans une tête, pas dans le dépôt</text>
<rect x="610" y="20" width="270" height="200" rx="12" fill="#151b2e" stroke="#ff6b8a" stroke-width="1.5"/><text x="745" y="44" text-anchor="middle" fill="#ff6b8a" font-weight="700">refusé</text>
<g fill="#f1f3ff"><text x="630" y="72">permission de déploiement pour l'agent</text><text x="630" y="92">DESTROY sur un construct partagé</text><text x="630" y="112">dynamodb:* sur * « à resserrer plus tard »</text><text x="630" y="132">secrets tournés → SSM (fusionné à moitié)</text></g>
<text x="745" y="170" text-anchor="middle" fill="#9aa3c7">tous sur le rayon d'impact ou l'histoire</text><text x="745" y="188" text-anchor="middle" fill="#9aa3c7">aucun sur la capacité</text><text x="745" y="208" text-anchor="middle" fill="#9aa3c7">tous attrapés à la PR</text>
<text x="450" y="242" text-anchor="middle" fill="#9aa3c7">Environ un tiers des commits d'infra en six mois ont été rédigés par l'agent. Zéro ont été déployés par l'agent.</text>
</g>
</svg>
</div>

## Ce que dit le motif

Les fusions sont du travail mécanique, bien documenté, avec une seule réponse juste. Les corrections sont des endroits où la réponse juste dépendait de quelque chose qui n'était pas dans le dépôt : le bastion, la forme du trafic. Les refus portent tous sur le rayon d'impact ou l'histoire, et aucun sur la capacité : dans chaque cas, l'agent pouvait produire la version sûre dès qu'on la lui demandait.

Le travail n'est donc pas « l'agent peut-il écrire du code d'infrastructure ». Il le peut. Le travail consiste à faire en sorte que le dépôt contienne ce qui vivait dans les têtes : la règle sur les politiques de suppression, le rôle du bastion, pourquoi les deux secrets tournent. Chaque refus ci-dessus est devenu une ligne dans le CLAUDE.md ou un commentaire dans le construct, et la même proposition n'est pas revenue.

Et la frontière du bac à sable n'est pas négociable, aussi bon que soit l'argument d'efficacité. L'agent rédige ; l'humain déploie. C'est toute la raison pour laquelle un tiers de l'infrastructure peut être rédigé par l'agent sans que personne ne perde le sommeil.

Si vous voulez utiliser un agent sur votre infrastructure et cherchez où placer la ligne, [nous l'avons tracée une fois et nous vous aiderons à tracer la vôtre](/contact).
