Back to zerosuite
zerosuite

Le serveur que nous n'avons pas résilié : vider une machine de production en une nuit, et chaque chiffre vérifié

Une nuit, six applications migrées à la ligne près avant 6 h 30, trois sauvegardes vides, une sans destination, et un serveur dont le sort a changé trois fois.

Juste Thales Gnimavo & Claude | October 2, 2026 19 min zerosuite
EN/ FR/ ES
caspclaude-codeclaude-opus-5.5infrastructuremigrationeasypanelpostgresbackupsdisaster-recoveryssh-hardeningchatwootproven-not-fixedceo-decisionssession-lifecycle

Par Juste Thales Gnimavo (CEO, ZeroSuite) & Claude Opus 5.5 — instance Claude Code

Note sur les clients : quatre des applications déplacées pendant cette session appartiennent à des clients de ZeroSuite. Elles sont désignées comme applications clientes A à D. Les adresses des serveurs, les noms d'hôte des sites clients et tous les identifiants sont omis.

Le prompt de la session portait un titre limpide : vider un ancien serveur dédié, déplacer tout ce qui vivait encore vers le serveur de production, puis résilier l'ancien. À la fin de la session, neuf heures plus tard, tout avait été déplacé. La résiliation n'a jamais eu lieu, et ce serveur est désormais la machine la plus importante de la prochaine phase de l'entreprise.

Cet article est le journal de cette nuit et du matin qui a suivi : six applications de production migrées avec des comptages de lignes identiques, une plateforme de paiement basculée en quarante secondes d'interruption, trois archives de sauvegarde qui se sont révélées vides, une sauvegarde « ok » qui n'avait nulle part où aller, et un serveur Chatwoot sauvegardé et restauré à l'essai peu avant que le CEO ne l'efface lui-même.

Le fil conducteur tient en une règle de notre doctrine d'exploitation, écrite il y a des mois pour un autre projet : « corrigé » n'est pas un état d'arrivée. Une chose est faite quand une observation sur une cible réelle le dit, avec la commande et sa sortie. Pas avant.


Partie 1 — Une fenêtre de 1 h 55 à 6 h 30

L'ancien serveur, une machine dédiée Hetzner que nous appelons thales-deblo, avait accumulé trois ans de projets : des sites clients, un ERP pour un client de logistique, la base de données et le Redis de notre plateforme de paiement 0fee, et un cimetière de projets morts que personne n'avait regardés depuis un an.

La session avait commencé la veille au soir par les deux étapes qui ne coûtent rien si elles sont bien faites : l'inventaire, puis la sauvegarde. Trente-quatre fichiers, 620 Mo, SHA256SUMS vérifiés 34/34 à trois endroits (l'ancien serveur, le nouveau, le Mac du CEO).

À 1 h 55, le CEO a écrit : *« Il est 01 h 55, on a jusqu'à 6 h 30 pour tout migrer : les sites n'ont pas de visiteurs maintenant. »* Quatre heures et demie sans trafic, pour six applications. Il s'occupait du DNS chez trois fournisseurs ; l'agent, de tout le reste.

Ce qui rendait cette fenêtre réaliste n'était pas la vitesse. C'était une méthode arrêtée avant que le premier octet ne bouge :

  1. Créer sur la cible, vide et à l'arrêt. Chaque service était créé par l'API REST
  2. d'Easypanel sur le nouveau serveur, à zéro réplique et sans domaine.
  3. Tirer, pas pousser. Le nouveau serveur récupérait les données de l'ancien par une clé
  4. SSH dédiée, restreinte à l'IP du nouveau serveur avec from=, sans transfert d'agent, sans
  5. redirection de port, sans pty. Les fichiers sshd_config et authorized_keys d'origine
  6. avaient d'abord été copiés en .bak.
  7. Dump final avec la source à l'arrêt, restauration, puis un script qui compte chaque ligne
  8. de chaque table des deux côtés et compare les deux listes. Il n'affiche que l'une de deux
  9. choses : COMPTAGE IDENTIQUE ou ECART.
  10. Le domaine seulement après le DNS. Le domaine n'est attaché à la cible qu'une fois le
  11. changement DNS du CEO visible depuis l'extérieur.

L'étape 3 résume tout l'article. Une restauration qui sort en 0 prouve que pg_restore a tourné. Elle ne prouve pas que les données sont arrivées. Une comparaison des comptages table par table, si :

ApplicationTablesLignesFichiers
Application cliente A707 6184 fichiers
Application cliente B118990 fichiers, 122,6 Mo
Application cliente C543 31067 fichiers, 45 Mo
Application cliente D (ERP logistique)862 705pas de volume
Un cinquième sitebase vide

Chaque ligne : COMPTAGE IDENTIQUE. Seize noms d'hôte en ligne en HTTPS à 2 h 52.


Partie 2 — Ce qui a cassé, et pourquoi ça n'a presque rien coûté

Rien de ce qui a cassé cette nuit-là n'était spectaculaire. Tout relevait du genre de problème qui transforme une fenêtre de quatre heures en fenêtre de sept quand on le découvre au mauvais moment.

Le certificat autosigné. Sur le premier site, le domaine a été attaché au nouveau serveur avant la bascule DNS. Traefik a tenté d'obtenir un certificat Let's Encrypt, a échoué au challenge, et a continué à servir le certificat autosigné CN=Easypanel d'Easypanel après la bascule. Le correctif : supprimer puis recréer le domaine. La leçon est entrée dans la méthode sous forme d'étape 4, et le problème ne s'est plus reproduit. Il comptait aussi pour une raison moins évidente : Let's Encrypt autorise cinq validations échouées par nom d'hôte et par heure. Réessayer par réflexe nous aurait bloqués pour le reste de la fenêtre.

L'anycast n'est pas instantané. Deux des fournisseurs DNS partagent les mêmes adresses anycast. Une région voyait le nouvel enregistrement plusieurs minutes avant une autre, et Let's Encrypt valide depuis plusieurs points d'observation. Un site a raté son premier certificat et l'a obtenu au second essai, à 2 h 32. Pour un domaine racine en retard chez un fournisseur, l'agent a attendu que les deux serveurs faisant autorité soient d'accord avant d'attacher le domaine, et a consigné pourquoi : quelques minutes d'interruption pour ce site vers 2 h 30, plutôt que de relancer l'ancienne application et de la laisser écrire dans une base sur le point d'être abandonnée.

Le volume qui semblait vide. Les volumes Easypanel sont des bind mounts. Lus depuis le chemin Docker conventionnel _data pendant que le conteneur est arrêté, ils paraissent vides. La première copie d'un dossier d'envois d'un client est revenue sans rien dedans. La lecture depuis le vrai chemin du bind mount a réglé le problème.

Un bug de guillemets dans un recomptage. Un $$ dans une commande shell distante a été avalé par le mauvais shell, et un recomptage a échoué. L'agent n'a pas rafistolé le comptage. Il a relancé toute la migration de cette application depuis la source arrêtée, parce que les deux dumps avaient des comptages source identiques et qu'une reprise complète était le seul résultat qu'il pouvait défendre.

La règle de la coupure de session. À 3 h, le CEO a dit : migre l'ERP, puis clôture cette session et continue dans une nouvelle. La session a écrit son log, son runbook et son état CASP, a poussé, et a envoyé le résumé WhatsApp. Puis la conversation a continué, parce que le CEO n'avait pas fini. On y revient dans la partie 8.


Partie 3 — Une plateforme de paiement, en plein jour, en quarante secondes

L'application de 0fee tournait déjà sur le nouveau serveur. Sa base de données et son Redis, non : le backend et le worker les atteignaient sur l'ancien serveur par l'internet public, avec sslmode=disable et un Redis non chiffré. Le prompt avait classé cette vague comme une décision du CEO, à faire en plein jour, parce qu'il s'agit d'infrastructure de paiement.

À 7 h, le CEO a écrit : « Tu t'occupes de 0fee. »

L'agent a mesuré avant de toucher quoi que ce soit :

  • Base de 10 Mo, 39 tables, 1 616 lignes ; dernière session de paiement le 19 septembre.
  • Seulement deux clients de cette base et de ce Redis, confirmés en cherchant dans
  • l'environnement de chaque service de l'ancien serveur.
  • Redis : douze clés. Trois sessions du tableau de bord, deux compteurs de limitation de débit,
  • des liaisons Celery qui se reconstruisent au démarrage, et **aucune tâche en attente dans
  • aucune file**.

Puis une répétition, production toujours en marche : dump complet, restauration dans le nouveau Postgres, comparaison des comptages. COMPTAGE IDENTIQUE en 2,3 secondes.

Puis le vrai passage :

text07:10:55  backend and worker scaled to 0
          final dump → COMPTAGE IDENTIQUE, 0 restore errors
          7 variables rewritten in Easypanel (DATABASE_URL, REDIS_*, CELERY_*)
          docker service update --env-add … --replicas 1   (same image, no rebuild)
07:11:37  both services back at 1/1

Quarante-deux secondes. La preuve n'était pas l'endpoint de santé, qui répond sans toucher la base. C'était GET /v1/countries et GET /v1/payin-methods via l'API publique, qui renvoyaient 200 pays et 115 moyens de paiement (la source en avait 115), les deux requêtes visibles dans les logs du nouveau backend, pendant que l'ancienne base ne montrait qu'une seule connexion : la requête de contrôle de l'agent lui-même.

Deux décisions ont été prises sans le CEO et consignées comme telles, chacune avec son moyen de revenir en arrière en une ligne :

  • Les clés Redis n'ont pas été copiées. Le coût : trois personnes connectées au tableau de
  • bord de 0fee devraient se reconnecter. Une copie aurait obligé à synchroniser un fichier RDB
  • avec une configuration AOF active, pour trois sessions.
  • **Les variables ont été appliquées par docker service update, pas par un redéploiement
  • Easypanel.** Un redéploiement reconstruit l'image depuis la branche principale. En pleine
  • bascule de paiement, cela aurait mis en production, par effet de bord, tout ce qui se
  • trouvait sur main. Easypanel porte les mêmes valeurs, donc le prochain déploiement normal
  • reste cohérent.

De nouveaux mots de passe ont été générés pour les deux services. Aucun ancien identifiant n'a été réutilisé, et aucun n'est apparu dans le terminal, dans le log ni dans cet article.


Partie 4 — Trois sauvegardes vides

Puis le CEO a changé le plan. *« On ne supprime pas ce serveur : son objectif est de l'utiliser pour 0seat.dev. On le réinstalle après migration de tous les services. »*

Une réinstallation efface le disque. La question est donc devenue : pour chaque projet mort de ce serveur, la sauvegarde est-elle assez bonne pour le ramener un jour où plus personne ne se souviendra de rien à son sujet ?

L'agent est revenu à la sauvegarde de 620 Mo de la veille, celle aux 34/34 sommes de contrôle vérifiées, et a listé chaque fichier avec sa taille :

text110 ./volumes/deblo-ai_fne_fne-data.tgz
109 ./volumes/deblo-ai_santecloud_santecloud-sqlite-db.tgz
110 ./volumes/client-d_filebrowser_database.tgz

Trois archives d'une centaine d'octets. Un tar vide compressé pèse à peu près cela. Les vrais dossiers contenaient invoices.json, users.json et une base SQLite. Les sommes de contrôle étaient parfaites, parce qu'une somme de contrôle prouve qu'un fichier n'a pas changé. Elle ne dit rien de ce qu'il contient. Le bug de chemin de volume de la partie 2 avait été corrigé pour la migration, mais ces trois archives dataient de la veille du correctif.

L'agent a donc reconstruit l'archive, et changé de méthode au passage :

  • Des dumps logiques, pas des dossiers de données. Pour chaque Postgres, le dossier de
  • données a été copié, la copie montée dans un conteneur postgres:17 lancé avec
  • --network none, puis exportée avec pg_dump -Fc. Postgres a aussitôt justifié ce choix par
  • un avertissement : les données avaient été créées avec glibc 2.36 et l'image actuelle tourne
  • sous 2.41, soit un écart de version de collation. Un dossier de données brut restauré sur une
  • image plus récente hérite de ce problème. Un dump logique, non.
  • Un test de restauration, pas une somme de contrôle. Chaque dump a été restauré dans une
  • base vierge et compté contre les comptages relevés à la source. Neuf bases, neuf IDENTIQUE.
  • La plus grosse était l'ancienne plateforme de paiement de Déblo : 113 tables,
  • 1 380 661 lignes. Une restauration a affiché une erreur : un droit accordé à un rôle nommé
  • admin, qui existe dans le globals.sql de l'archive. Le runbook dit désormais de restaurer
  • ce fichier en premier.

L'archive finale couvre les sept projets du serveur, morts ou migrés : 64 fichiers, 719 Mo, vérifiés par somme de contrôle sur le serveur de production et sur le Mac.

La question suivante du CEO : fallait-il louer un petit serveur Easypanel à 11 $ par mois pour garder les projets morts en vie et prêts à servir ? La première réponse de l'agent a été non : un projet mort a besoin de ses données, pas d'un serveur qui tourne, et une machine oubliée qui porte du code de paiement et de transfert d'argent est un passif plutôt qu'une assurance. Une heure de restauration, face à une licence qui prendra des mois, ne justifiait pas 132 $ par an.

Le CEO a alors fait remarquer que le projet de transfert d'argent avait déjà son propre serveur, utilisé uniquement pour son Chatwoot. **L'agent a changé de position et a dit pourquoi.** Le coût n'était plus un nouveau serveur, seulement le maintien d'un serveur existant. Et une restauration sur un vrai Easypanel prouve ce qu'aucune archive ne prouve : que chaque projet démarre réellement. C'est ce second argument qui compte. Une archive restaurée une fois est un fait. Une archive dont on a seulement vérifié la somme de contrôle est un espoir.


Partie 5 — « Backup ok »

Le CEO a activé les sauvegardes programmées des bases de données d'Easypanel pour les bases migrées, et a écrit : « backup 0fee ok sur le nouveau serveur. »

L'agent a listé les configurations de sauvegarde par l'API. Six bases, toutes activées. Cinq pointaient vers le bucket S3 de Hetzner. La sixième, celle de 0fee, avait un **identifiant de fournisseur de stockage vide**. Elle aurait tourné toutes les heures, pour toujours, sans rien envoyer nulle part. Le tableau de bord la montrait activée, et c'est précisément ce qui rendait « ok » raisonnable à dire.

L'agent l'a rattachée au même bucket (réversible en un appel d'API, consigné comme décision prise sans le CEO), l'a lancée à la main, puis a fait l'étape qui transforme « configuré » en « fait » : il a lu les identifiants S3 depuis Easypanel en mémoire, sans jamais les afficher, et a listé le bucket.

textzerosuite/0fee_postgres      1 file  ('2026-10-02 07:55', 73670)
client-c/postgres            0 files
...

Les cinq autres étaient vides aussi, pour une raison moins inquiétante : leur première exécution programmée n'avait pas encore eu lieu. Une exécution manuelle chacune, quarante secondes d'attente, et la liste affichait six fichiers. C'est seulement à ce moment que le runbook a dit prouvé.

La doctrine a une formule pour ce que le log de session portait jusque-là : Preuve due: <observation> — sur <cible> — bloquée par <ce qui manque>. Une preuve due. Ce n'est pas un état d'échec ; c'est l'état honnête de quelque chose de configuré et pas encore observé. L'erreur est de sauter cette étape.


Partie 6 — Sauvegardé juste avant d'être effacé

Le serveur du projet de transfert d'argent faisait tourner une installation native de Chatwoot, sans Docker : Rails, nginx, Postgres 16 et Redis directement sur la machine, en version 4.4.0. Le CEO voulait qu'il soit sauvegardé, puis le serveur reconstruit avec Easypanel pour héberger les projets morts.

Le premier message de l'agent sur le sujet a été une contrainte, pas un plan : **sauvegarder Chatwoot d'abord, installer Easypanel ensuite, non négociable.** Easypanel prend les ports 80 et 443 pour son propre proxy et bascule Docker en mode Swarm. Installé par-dessus un Chatwoot en marche, il casse le site de support.

L'inspection, en lecture seule, a trouvé la matière du plan, et deux choses que personne n'avait demandées :

  • 1 115 conversations, 4 311 messages, 199 pièces jointes (78 Mo) stockées dans un bucket S3
  • plutôt que sur le disque. Quinze messages sur les trente derniers jours.
  • Le port 3000, celui du serveur Rails, joignable depuis internet sans passer par nginx,
  • sur une machine sans pare-feu activé.
  • Quatre comptes créés par des inconnus, alors que l'inscription était désactivée : test
  • deux fois en mai, vulncheck en juillet, poc-zjr5 le lendemain, depuis des adresses en
  • scanner.test et test.local. Leurs utilisateurs n'ont jamais confirmé d'e-mail ni ne se
  • sont jamais connectés. Les logs nginx de ces dates avaient été effacés par la rotation : on ne
  • peut plus établir comment ils sont entrés.

L'agent l'a consigné comme constat, n'a rien supprimé, et a recommandé de ne pas restaurer ces quatre comptes. Puis il a sauvegardé la base, les globals et la configuration, et a **restauré le dump à l'essai** dans un postgres:16 vierge : 5 comptes, 1 115 conversations, 4 311 messages, 199 blobs, zéro erreur.

Un peu plus tard, le CEO a annoncé qu'il avait réinstallé le serveur depuis la console Hetzner, avec Ubuntu 24 et Easypanel. Le Chatwoot d'origine avait disparu. Restait la sauvegarde, restaurée une fois et comptée. La règle « sauvegarder d'abord » n'était pas de la prudence pour le principe. C'est elle qui a décidé si cette histoire aurait un trou.


Partie 7 — Ce qu'une réinstallation remet à zéro

Plus tôt ce matin-là, le CEO avait demandé de fermer la connexion SSH par mot de passe sur ce serveur. L'agent a constaté que authorized_keys ne contenait qu'une seule clé, la sienne, ce qui voulait dire que le CEO se connectait par mot de passe. Il l'a signalé avant de couper, a appliqué PasswordAuthentication no et PermitRootLogin prohibit-password dans un fichier drop-in, et a vérifié les deux sens : une nouvelle session par clé réussit, et une tentative par mot de passe reçoit Permission denied (publickey).

Après la réinstallation, la première inspection du nouveau système affichait de nouveau passwordauthentication yes. Une réinstallation rétablit les valeurs par défaut du fournisseur, y compris celle qu'on a fermée une heure plus tôt. Le durcissement a été réappliqué et vérifié de la même façon.

Le jeton d'API du nouvel Easypanel est passé par le presse-papiers du CEO. L'agent l'a lu avec pbpaste dans un fichier en mode 600, n'en a affiché que la longueur, l'a testé par un appel listProjects qui a renvoyé 200 [], et a vidé le presse-papiers. Quand le CEO a proposé d'ajouter le serveur avec une clé privée existante, l'agent a plutôt généré une clé dédiée, pour qu'un accès puisse être révoqué sans toucher aux autres.


Partie 8 — Un serveur dont le sort a changé trois fois

Au fil de la session, le plan pour thales-deblo a changé trois fois :

  1. Le résilier après une semaine d'observation (le prompt de la session).
  2. Le garder et le réinstaller pour 0seat.dev une fois tout migré (le CEO, à 7 h).
  3. Il remplace le nouveau serveur que nous allions acheter (le CEO, à 7 h 40). Cela a
  4. supprimé purement et simplement un achat de la prochaine phase. L'agent a mesuré la machine
  5. avant de convenir qu'elle faisait l'affaire : Ryzen 7 7700, 64 Go, deux NVMe de 1 To en RAID,
  6. et un troisième NVMe de 2 To qui n'est même pas monté, à Helsinki. C'est une meilleure
  7. machine que celle du plan.

Aucun de ces changements ne revenait à l'agent. Ce qu'il a fait à chaque fois, c'est mettre à jour chaque endroit qui portait l'ancienne décision : le tableau des décisions du CEO dans le CLAUDE.md du projet, le prompt de la session (avec un bandeau « révisé » daté plutôt qu'une réécriture silencieuse), le runbook, le prompt suivant dans la file, et l'état CASP. La période d'observation est passée de sept jours à deux, sur décision du CEO, et l'agent a approuvé pour une raison énoncée : une archive entièrement vérifiée joue désormais le rôle de filet de sécurité que tenaient les données de l'ancien serveur. Il en a aussi nommé la limite : 0fee n'avait reçu aucun paiement depuis le 19 septembre, donc deux jours d'observation ne mettront pas un vrai paiement à l'épreuve sur la nouvelle base.

La session avait déjà été compactée une fois à ce stade. Notre doctrine dit : après une compaction, terminer le chantier en cours et ne pas en ouvrir un nouveau. Quand le CEO a demandé la restauration des projets morts sur le serveur instanly, l'agent a fait l'inspection, la sauvegarde et le durcissement (quelques minutes chacun, chacun achevant quelque chose de déjà commencé), et a écrit la restauration elle-même dans un nouveau prompt de session, avec une section DO NOT ouverte par la phrase qui compte le plus pour des projets qui manipulent de l'argent : **ne démarrer aucun worker, cron ni file de ces projets sans avoir d'abord lu son code et ses variables.** Puis il a livré la phase dans CASP et a dit au CEO qu'il pouvait fermer.


Ce que nous retenons de cette session

  1. Une restauration qui sort en 0 prouve que la commande a tourné. Une comparaison des
  2. comptages de lignes table par table prouve que les données sont arrivées. Scriptez-la pour
  3. qu'elle ne puisse afficher que l'un de deux mots.
  4. **Une somme de contrôle prouve qu'un fichier n'a pas changé, pas qu'il contient quoi que ce
  5. soit.** Trois de nos 34 archives vérifiées étaient des tars vides.
  6. Sauvegardez les bases en dumps logiques à partir d'une copie, dans un conteneur sans
  7. réseau, et restaurez chacune une fois. Les dossiers de données bruts emportent leur glibc
  8. avec eux.
  9. « Activé » ne veut pas dire « sauvegardé ». Une sauvegarde programmée est prouvée quand
  10. un fichier apparaît dans la liste de la destination.
  11. Répétez la bascule avec la production en marche. 2,3 secondes mesurées valent mieux que
  12. 40 secondes promises.
  13. Sauvegardez avant d'installer, à chaque fois, même quand le serveur « ne fait tourner
  14. qu'un Chatwoot ».
  15. Une réinstallation remet votre durcissement à zéro. Revérifiez-le dès la première
  16. connexion.
  17. Quand le CEO change le plan, mettez à jour chaque document qui portait l'ancien, avec un
  18. bandeau daté là où un lecteur pourrait détenir l'ancienne version.
  19. Changez de position à voix haute. Dites quel argument nouveau vous a fait bouger.
  20. « Tu as raison » n'est pas un argument.

CASP — le Coding-Agent State Protocol. Votre agent IA mène toute la feuille de route, sans jamais perdre le fil. Natif Git, entièrement local, MIT, zéro télémétrie. Conçu par Juste Thales Gnimavo de ZeroSuite, CEO solo dont les produits tournent en production avec Claude pour seul ingénieur. Installation : npm i -g @justethales/casp · https://casp.sh · https://github.com/ThalesGnimavo/casp
Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude zerosuite

L'orchestrateur ne code pas : treize sessions en une journée, un navigateur qui vérifie, et savoir quand s'arrêter

Une journée, une plateforme client privée, treize sessions Claude headless enchaînées par un orchestrateur qui n'écrit jamais de code. Vérifier dans le navigateur du CEO, grouper les correctifs sans lâcher la gate, et écrire quand fermer une session.

19 min Sep 30, 2026
caspclaude-codeclaude-opus-5.5multi-session +12
Thales & Claude zerosuite

Une recrue qui n'écrit que des notes privées : brancher une IA sur un support client en production sans la laisser parler à un seul client

Un jour, un support client, 12 166 conversations passées : comment nous avons branché une IA sur un support Chatwoot en production, en mode brouillon uniquement. Elle écrit des notes privées, cite ses sources ou passe la main, lit les captures d'écran mais jamais les PDF, et chaque appel au modèle a un prix. Quatre audits, erreurs comprises.

28 min Sep 28, 2026
chatwootcustomer-supportragpgvector +9
Thales & Claude zerosuite

Aucun code livré : mener une recherche d'emploi comme un projet logiciel, avec une IA qui prépare tout et n'envoie rien

Un jour, une session avec une IA, aucune ligne de code produit : une recherche d'emploi menée avec les outils que ZeroSuite utilise pour livrer ses logiciels, en méthode valable pour tout métier. Un dépôt privé, un seul fichier de suivi où « envoyé » veut dire « daté », des règles qui refusent la phrase improuvable, une feuille de route CASP qui se termine au contrat signé, et une automatisation qui remplit les brouillons sans jamais appuyer sur Envoyer. Avec un guide pas à pas à télécharger.

18 min Sep 24, 2026
job-searchcareercaspclaude-code +7