Les environnements auto-hébergés sont en bêta publique sur les plans Team et Enterprise ; Disponibilité et limitations couvre le chemin d’activation. Cette page couvre l’exécution de la flotte en production ; consultez le guide de démarrage rapide pour votre premier runner et session.
Sécurisez votre déploiement
Un runner auto-hébergé exécute du code arbitraire, dirigé par le modèle, sur votre infrastructure au nom de tous ceux qui peuvent dispatcher une session vers son environnement. Il s’agit de tout membre de votre organisation Anthropic, et de toute personne qui peut démarrer une session de canal Claude Tag dans une portée qu’un propriétaire a routée vers l’environnement. Travaillez sur chaque élément avant de connecter un environnement à des systèmes de production :-
Conteneurs éphémères, par session : exécutez chaque processus runner dans un conteneur ou une VM fraîche qui est détruite lorsque le processus se termine, avec
--capacity 1et la valeur par défaut--drain-grace-sec 0afin que chaque conteneur serve exactement une session. À une capacité plus élevée, ou avec une période de drainage positive, un conteneur sert plusieurs sessions du même propriétaire verrouillé ; voir Cycle de vie du runner. Ne réutilisez pas un système de fichiers entre les redémarrages du runner, sauf dans la configuration délibérée checkout pré-chauffé, et jamais entre les propriétaires. -
Pas de larges identifiants dans l’image : n’incluez pas de clés SSH de longue durée, d’identifiants de fournisseur cloud, ou de jetons d’accès personnel qui accordent plus que ce qu’une session a besoin. Générez les identifiants utilisés pendant une session, tels que les jetons push ou API, par session à partir de votre script wrapper. Pour le clone initial, qui se produit avant l’exécution du wrapper, utilisez un hook de cycle de vie
checkoutou--use-anthropic-git-proxy; voir Configurer git. - Gardez le secret de l’environnement loin des hôtes exécutant les sessions : le secret de l’environnement peut enregistrer des runners et récupérer toute session mise en file d’attente sur l’environnement. Sur une flotte fixe, il réside sur chaque hôte runner, où le code de toute session peut lire le fichier secret. Préférez les runners à la demande, où le secret reste sur l’hôte orchestrateur, qui n’exécute jamais de code utilisateur, et chaque runner reçoit un bon de travail à usage unique qui enregistre exactement un runner. Sur une flotte fixe, traitez le fichier secret-environnement comme lisible par toute session et faites tourner le secret après tout compromis de session suspecté.
- Sortie réseau par défaut-refuser : limitez le trafic sortant du conteneur runner et session à votre propre limite réseau sur chaque environnement ; Sortie par défaut-refuser couvre ce qu’il faut autoriser et pourquoi.
- IAM hôte avec privilèges minimaux : l’identité de calcul attachée à l’hôte runner, telle qu’un profil d’instance ou un compte de service de nœud, ne devrait accorder que ce dont le runner lui-même a besoin. Les sessions devraient obtenir leurs propres identifiants via votre script wrapper plutôt que d’hériter de ceux de l’hôte.
-
Bloquez le point de terminaison des métadonnées cloud des sessions : garder les sessions hors de l’identité hôte nécessite de bloquer leur accès au point de terminaison des métadonnées, et les politiques de sortie au niveau du sous-réseau n’interceptent pas le trafic de métadonnées link-local, donc bloquez-le dans le conteneur lui-même :
- IMDSv2 avec une limite de saut d’un
- GKE Workload Identity avec dissimulation des métadonnées
- Un refus explicite pour
169.254.169.254dans l’espace de noms réseau du conteneur de session
-
Isolation du système de fichiers par runner : chaque processus runner obtient son propre répertoire de travail qu’aucun autre processus sur l’hôte ne peut lire ou écrire. Rendez
--hooks-dir, le script wrapper, et le~/.claude/de l’hôte en lecture seule pour la session, soit intégrés dans l’image, soit montés en lecture seule. -
Dispatch n’a pas de contrôle d’accès par environnement : tout membre de votre organisation Anthropic peut dispatcher une session vers n’importe lequel de ses environnements. Si un propriétaire route les canaux Claude Tag vers l’environnement, toute personne que le paramètre d’accès Claude Tag admet peut démarrer des sessions de canal qui s’y exécutent. Par défaut, il s’agit de toute personne dans l’espace de travail Slack connecté, avec ou sans compte Claude. Traitez chaque hôte runner comme accessible pour l’exécution de code par tous ceux qui peuvent dispatcher vers lui, et placez sur un hôte runner uniquement les données et identifiants que toutes ces personnes sont autorisées à lire.
--lock-to-accountlimite les sessions de quel compte un hôte donné exécute, mais cela ne réduit pas qui peut dispatcher dans l’environnement. Pour faire des environnements auto-hébergés la seule option de sélection, un propriétaire peut masquer les environnements hébergés par Anthropic pour toute l’organisation à partir de la page Environnements cloud. -
Appliquez la garde des paramètres de dépôt : choisissez le mode de garde avec
--confine-repo-settings. La valeur par défautwarnenregistre une violation et lance quand même la session,enforcerefuse la session, etoffdésactive l’analyse. Le runner analyse les paramètres validés de chaque dépôt pour :- Une autorisation qui se résout en dehors de l’espace de travail propre de cette session : une entrée
additionalDirectories, une règleEdit,Write, ouNotebookEditdanspermissions.allow, ou une entréesandbox.filesystem.allowWriteouallowRead - Un bloc
envnon vide - Un remplacement de posture d’opérateur tel que
sandbox.enabled: false
--trust-workspace, et ne couvre pas les hooks de dépôt,.mcp.json, ou les règles Bash ; voir Permissions et approbation des outils pour savoir où ces autorisations doivent se trouver. - Une autorisation qui se résout en dehors de l’espace de travail propre de cette session : une entrée
La liste d’autorisation IP de votre organisation ne couvre pas le trafic du runner auto-hébergé par défaut. Ne vous fiez pas à elle comme contrôle réseau pour le trafic du runner ou de la session ; appliquez plutôt une sortie par défaut-refuser à votre propre limite réseau, et contactez votre équipe de compte Anthropic si vous souhaitez l’application de la liste d’autorisation IP pour votre organisation.
Exigences réseau
Le runner et les enfants de session qu’il génère établissent des connexions sortantes vers les hôtes ci-dessous. Limitez la sortie du conteneur de session à ces hôtes et aux services internes spécifiques que les sessions doivent atteindre ; Sortie par défaut-refuser couvre comment et pourquoi. Ces hôtes sont toujours requis :
Que ces hôtes soient nécessaires dépend de votre configuration :
Le runner n’atteint pas
statsig.anthropic.com, *.sentry.io, claude.ai, ou platform.claude.com. Ces hôtes apparaissent dans certaines listes de contrôle réseau d’entreprise plus anciennes, mais vous n’avez pas besoin de les autoriser pour le trafic du runner ou de la session : les récupérations de drapeaux de fonctionnalités vont à api.anthropic.com, et le runner s’authentifie avec le secret de l’environnement plutôt qu’avec OAuth interactif. Deux flux côté hôte atteignent claude.ai, donc exécutez-les à partir d’un hôte dont la sortie le permet plutôt que d’élargir la sortie du conteneur de session : l’installateur d’une ligne récupère install.sh depuis claude.ai au moment de l’installation, et claude auth login interactif, que le guide de configuration, le mode signé du doctor, et la dispatch CI utilisent, se connecte via claude.ai, claude.com, et platform.claude.com. mcp-proxy.anthropic.com n’est pas requis non plus : les sessions auto-hébergées ne l’utilisent pas, et la livraison de vos connecteurs claude.ai d’organisation aux sessions, quand activée pour votre organisation, route via api.anthropic.com. Consultez Serveurs MCP.
Sortie par défaut-refuser
Déployez les conteneurs runner et session dans un segment réseau ou un espace de noms dont le trafic sortant est limité aux hôtes du tableau des exigences réseau, votre hôte git, et les services internes spécifiques que les sessions doivent atteindre. Le produit ne peut pas vérifier ou appliquer cela, donc appliquez-le à votre propre limite réseau sur chaque environnement. Le code de session est dirigé par le modèle et peut tenter des connexions vers des hôtes arbitraires ; la sortie par défaut-refuser au niveau réseau limite où ces tentatives peuvent atterrir. Cela s’applique indépendamment du mode de permission : l’ensemble d’outils pré-approuvé par défaut inclut déjàBash, donc la sortie shell s’exécute sans invite même sans mode auto.
Pour plus de détails sur la télémétrie que chaque session émet et comment la désactiver, consultez Télémétrie.
S’authentifier auprès d’un proxy de sortie
Certains proxies de sortie d’entreprise nécessitent un en-têteProxy-Authorization sur chaque connexion. Le jeton dans cet en-tête tourne souvent trop vite pour être écrit dans l’URL du proxy que vous définissez dans HTTPS_PROXY. Définissez HTTPS_PROXY ou HTTP_PROXY sur l’URL de votre proxy comme d’habitude, puis définissez --proxy-authorization-command ou --proxy-authorization-file pour dire au runner où lire la valeur de l’en-tête. Les deux drapeaux nécessitent Claude Code v2.1.238 ou plus récent.
Choisir d’où provient la valeur Proxy-Authorization
Choisissez le drapeau qui correspond à la façon dont vous produisez le jeton Proxy-Authorization :
--proxy-authorization-command <command>: choisissez ceci pour un jeton que vous générez à la demande. Le runner exécute la commande shell et utilise sa stdout rognée comme valeur d’en-tête, par exempleBearer <token>.--proxy-authorization-file <path>: choisissez ceci pour un jeton qu’un autre processus fait tourner en place. Le runner lit le fichier et utilise son contenu rogné comme valeur d’en-tête.
Configurations que le runner refuse de démarrer avec
Chaque drapeau a également une forme de variable d’environnement, listée à côté dans la référence des drapeaux CLI du runner. Avant que le runner ne contacte votre proxy ou le plan de contrôle, il vérifie les drapeaux et leurs variables, et refuse de démarrer dans trois cas :- Les deux drapeaux définis : un drapeau plus la variable d’environnement de l’autre drapeau compte comme la définition des deux.
- Pas d’URL de proxy : ni
HTTPS_PROXYniHTTP_PROXYne contient une URLhttp://ouhttps://. Le runner lit les deux variables en majuscules ou minuscules, et ne consulte pasALL_PROXY. - L’un ou l’autre drapeau passé à la sous-commande orchestrateur :
self-hosted-runner orchestratorn’accepte pas les drapeaux ou leurs variables d’environnement. Passez le drapeau à chaque runner que l’orchestrateur démarre à la place.
Ce que le runner change quand un drapeau d’autorisation de proxy est défini
Avec l’un ou l’autre drapeau défini, le runner démarre son propre écouteur et envoie le trafic proxy de lui-même, ses hooks de cycle de vie, et ses sessions via cet écouteur. L’écouteur ajoute l’en-têteProxy-Authorization en route vers votre proxy.
- Écouteur : l’écouteur est un proxy avant sur
127.0.0.1. Le runner démarre l’écouteur avant de s’enregistrer auprès du plan de contrôle, et quitte au démarrage si l’écouteur ne peut pas démarrer. - Variables de proxy : le runner réécrit lequel de
HTTPS_PROXYetHTTP_PROXYvous avez défini pour qu’il pointe vers l’écouteur. Cette valeur réécrite atteint le runner lui-même, ses hooks de cycle de vie, et chaque session qu’il exécute. - Rotation de jeton : un jeton pivoté prend effet sans redémarrage. Pour chaque connexion que l’écouteur ouvre vers votre proxy, le runner exécute votre commande ou relit votre fichier et ajoute le résultat comme en-tête.
- Environnement de session : une session atteint votre proxy uniquement via l’écouteur. Dans l’environnement de chaque session, le runner supprime
ALL_PROXY, supprime toute orthographe deHTTPS_PROXYouHTTP_PROXYque vous n’avez pas définie, et épingleNO_PROXYà la valeur du runner. - Journaux : le runner ne journalise jamais la valeur de l’en-tête.
Configurer git
Le runner gère les checkouts de référentiel mais ne configure pas l’identité git ou les identifiants par défaut. Vous contrôlez l’image et l’environnement de processus du runner, donc vous contrôlez la configuration git. Choisissez l’une de deux approches :- Laisser le runner configurer git : démarrez le runner avec
--configure-gitpour qu’il écrive la même identité et configuration de signature de commit que les sessions hébergées par Anthropic utilisent - Livrer la configuration git dans votre image : définissez l’identité et les identifiants push vous-même, par exemple pour committer sous votre propre identité de bot
--configure-git la signature de commit SSH nécessite Git 2.34 ou plus récent, --use-anthropic-git-proxy nécessite 2.32 ou plus récent, et reprendre les sessions à partir de branches poussées par --push-outcome-on-release nécessite 2.29 ou plus récent. Git 2.24 est suffisant si vous omettez les trois et gérez l’identité git vous-même.
Laisser le runner configurer git
Démarrez le runner avec--configure-git, ou définissez SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, pour qu’il écrive la configuration git globale au démarrage :
user.name = Claudeetuser.email = noreply@anthropic.com, correspondant aux sessions hébergées par Anthropic- Signature de commit et de tag au format SSH, routée via un shim géré par le runner qui signe chaque commit via le service de signature d’Anthropic en utilisant les identifiants de la session. Les signatures sont vérifiables sur GitHub par rapport à la clé de signature SSH publiée d’Anthropic.
Livrer la configuration git dans votre image
L’identité Git est requise pour tout commit. Définissez-la au niveau du système dans votre Dockerfile pour que la configuration s’applique indépendamment de l’utilisateur sous lequel le processus runner s’exécute :git commit échoue avec Please tell me who you are et les sessions ne peuvent pas progresser. Vous pouvez utiliser votre propre identité de bot à la place ; le runner ne remplace pas ces valeurs.
Ne cuisez pas les identifiants push longue durée ou largement scoped dans une image runner partagée : un identifiant dans l’image est disponible pour chaque session que l’image exécute, peu importe qui l’a démarrée. À la place, créez un jeton court-durée, least-scoped par session à partir de votre script wrapper, en utilisant l’identité du créateur de session décodée du JWT de session. Associez-le à un conteneur éphémère par session, qui nécessite --capacity 1, pour qu’aucun identifiant ne survive à la session qui l’a créé ; consultez la section de durcissement.
Si vous devez configurer les identifiants push au niveau de l’image, par exemple pour une clé de déploiement en lecture seule, limitez-les aussi étroitement que votre hôte git le permet :
- Une clé de déploiement SSH limitée à un référentiel avec une réécriture
url.<base>.insteadOf - Un
credential.helperqui retourne un jeton minimalement scoped GIT_SSH_COMMANDpointant vers une clé étroitement scoped
- Le runner définit
GIT_TERMINAL_PROMPT=0, donc git ne demande pas de nom d’utilisateur ou de mot de passe. - Le runner exécute SSH avec
BatchMode=yes, ajouté à votreGIT_SSH_COMMANDsi vous en définissez un, donc SSH ne demande pas de phrase de passe ou de confirmation d’hôte. - Le runner définit
GCM_INTERACTIVE=never, donc Git Credential Manager n’ouvre pas de dialogue de connexion. - Le runner efface
core.askPass, donc si vous utilisez un helper askpass, définissez-le via la variable d’environnementGIT_ASKPASSà la place.
safe.directory :
Utiliser le proxy git Anthropic
Démarrez le runner avec--use-anthropic-git-proxy, ou définissez CLAUDE_RUNNER_USE_GIT_PROXY=1, pour qu’il clone via le proxy git d’Anthropic, authentifié avec le jeton court-durée de la session. Pour les sessions utilisateur ordinaires, le proxy utilise le jeton OAuth GitHub ou GitHub Enterprise stocké pour le créateur de session ; pour les sessions de bot et d’agent, il utilise le jeton d’installation GitHub App de votre organisation. De toute façon, l’image runner n’a besoin d’aucun identifiant git : pas de clés SSH, pas de credential helper, pas de .netrc. C’est le même chemin d’authentification que les environnements hébergés par Anthropic utilisent.
Le proxy nécessite --capacity 1 car l’URL du proxy est par session, et git 2.32 ou plus récent car les anciennes versions de git ignorent le mécanisme de configuration que le proxy utilise pour isoler les sessions les unes des autres. Le runner refuse de démarrer si l’une ou l’autre exigence n’est pas satisfaite. Parce que le proxy récupère du côté d’Anthropic, votre hôte git doit être accessible depuis l’infrastructure Anthropic, la même exigence que les sessions hébergées par Anthropic ont ; pour un hôte git qui n’est routable que dans votre réseau, utilisez un hook de cycle de vie checkout à la place. Chaque processus runner gère une session à la fois, donc exécutez plus de répliques pour le parallélisme. Quand le proxy est activé, --git-host-rewrite et --git-ssh-rewrite n’ont aucun effet : l’URL du proxy pointe vers api.anthropic.com, pas votre hôte git.
Réécrire les URL git pour les réseaux privés
Les URL de référentiel arrivent du plan de contrôle en HTTPS, avec le nom d’hôte de votre hôte git ; pour GitHub Enterprise, c’est le nom d’hôte que vous avez configuré pour l’intégration GitHub Enterprise dans les paramètres d’administration de Claude Code sur claude.ai. Deux drapeaux répétables réécrivent ces URL avant le clone :--git-host-rewrite <from>=<to>: pour le DNS à horizon divisé, où Anthropic atteint votre hôte git via un nom d’hôte externe mais les runners doivent utiliser un interne--git-ssh-rewrite <host>: pour les hôtes git qui n’acceptent que SSH, réécrivanthttps://<host>/owner/repoengit@<host>:owner/repo
--git-ssh-rewrite si vous avez besoin des deux. Pour un contrôle complet du checkout, utilisez un hook de cycle de vie checkout.
Construire l’image du runner
Anthropic ne publie pas d’image runner pré-construite. Construisez la vôtre autour du binaireclaude, en superposant la chaîne d’outils que vos référentiels ont besoin : runtimes de langage, compilateurs, gestionnaires de paquets, et sidecars MCP.
Les recettes ci-dessous utilisent --capacity 4, donc un conteneur sert jusqu’à quatre sessions concurrentes du même propriétaire verrouillé. Cela ne fournit pas l’isolation du conteneur par session dans la section de durcissement : avant de connecter un environnement aux systèmes de production, soit exécutez les recettes à --capacity 1 avec un conteneur par session, soit utilisez les runners à la demande, qui gardent également le secret de l’environnement hors des hôtes exécutant les sessions.
Ce Dockerfile est un point de départ minimal :
linux-x64 pour linux-arm64 si vos nœuds sont ARM, ou pour linux-x64-musl ou linux-arm64-musl sur une image basée sur musl comme Alpine ; consultez Configuration Alpine Linux pour les paquets supplémentaires dont les images musl ont besoin. L’URL est l’emplacement de version standard de Claude Code, donc vous pouvez vérifier le binaire téléchargé par rapport au manifeste signé de la version comme décrit dans Intégrité binaire et signature de code. Construisez l’image avec Claude Code version 2.1.224 ou plus récent, puis poussez-la vers votre registre et référencez-la dans les recettes ci-dessous :
Dimensionner le CPU et la mémoire pour les sessions
Dimensionnez le conteneur ou l’hôte d’un runner pour les sessions qu’il exécute plutôt que pour le processus runner lui-même. Le runner lui-même interroge le système pour trouver du travail, prépare le checkout de chaque session, exécute vos hooks de cycle de vie, et démarre et supervise les processus de session. La charge provient des sessions : chacune est un processus Claude Code plus tout ce qu’il démarre, comme les builds, les suites de tests, les installations de paquets, et les serveurs MCP. Pour une session, commencez par les valeurs suivantes, exprimées comme des demandes et des limites Kubernetes ou l’équivalent de votre plateforme, et traitez-les comme un point de départ plutôt que comme une exigence :- Mémoire : une demande et une limite de 4 Gio chacune, ce qui satisfait le minimum de 4 Go dans les exigences système de Claude Code. Gardez les deux égales afin que le planificateur tienne compte de la mémoire complète du conteneur. Lorsque le conteneur atteint sa limite de mémoire, le noyau tue les processus à l’intérieur, ce qui peut terminer une session en cours de tâche.
- CPU : une demande de 2 CPUs et une limite de 4 CPUs, afin qu’une session puisse dépasser la demande lors des builds. Le noyau limite un conteneur à sa limite de CPU plutôt que de tuer les processus à l’intérieur, donc les sessions à la limite s’exécutent plus lentement mais continuent de s’exécuter.
resources suivant :
--capacity pour limiter le nombre de sessions qu’il exécute à la fois. Il ne divise pas le CPU ou la mémoire entre elles, donc les sessions sur un runner partagent le CPU et la mémoire du conteneur. Pour limiter la part d’une session, appliquez les limites de votre script wrapper. Ce qu’il faut donner à un conteneur dépend donc du nombre de sessions qu’il dessert à la fois :
- Une session par runner : donnez à chaque conteneur les valeurs d’une session. Utilisez ce dimensionnement à
--capacity 1, que la section de durcissement recommande, et pour les runners à la demande, où vous définissez les valeurs sur la charge de travail que votre hookspawn-runnersoumet, comme un modèle de pod de Job Kubernetes. - Plusieurs sessions par runner : à un
--capacitysupérieur à un, multipliez les valeurs d’une session par la capacité, car jusqu’à ce nombre de sessions peuvent s’exécuter dans le conteneur en même temps. Les recettes Kubernetes et Docker Compose exécutent--capacity 4sans limites de CPU ou de mémoire, donc ajoutez des limites dimensionnées pour la capacité que vous exécutez.
Kubernetes
Le runner sertGET /healthz sur le port 8080 par défaut, configurable avec --health-port, donc les sondes Kubernetes fonctionnent sans configuration supplémentaire. Le point de terminaison retourne 200 chaque fois que le processus est vivant, donc les sondes ci-dessous détectent un processus mort, pas un bloqué ; pour attraper un runner qui a arrêté d’interroger, alertez sur la série last_poll_age_seconds de /metrics. Le Deployment ci-dessous monte le secret de l’environnement à partir d’un Secret Kubernetes, pointe les sondes de vivacité et de disponibilité vers /healthz, et définit une période de grâce de terminaison de 90 secondes. Consultez Timing d’arrêt pour savoir pourquoi la période de grâce est importante.
Le manifeste ne définit pas de resources CPU ou mémoire sur le conteneur runner. Ajoutez un bloc dimensionné pour la capacité que vous exécutez, comme Dimensionner le CPU et la mémoire pour les sessions le décrit.
claude-runners. Créez d’abord l’espace de noms :
(umask 077 && cat > ./environment-secret), collez le secret, appuyez sur Entrée, puis Ctrl-D. Ensuite, créez le Secret et supprimez le fichier :
Docker Compose
Le service Compose ci-dessous redémarre le runner chaque fois qu’il se termine, ce qui couvre à la fois les crashes et la sortie normale après le drainage. Une politique de redémarrage Docker redémarre le même conteneur avec sa couche inscriptible intacte, donc le runner revient sur un système de fichiers réutilisé plutôt que le frais que la posture de durcissement recommande ; utilisez cette recette pour l’évaluation, et pour la production soit recréez le conteneur par exécution, soit utilisez un orchestrateur qui le fait.Délai d’arrêt
À la réception deSIGTERM, le runner cesse de prendre de nouveaux travaux et, sauf si vous définissez --defer-shutdown-max-min, attend jusqu’à --drain-wait-sec, zéro par défaut, que les tours en cours se terminent, termine l’arborescence des processus de chaque session, et exécute le hook de cycle de vie post-session. Cette arborescence de processus inclut les commandes que Claude exécutait encore dans la session.
Le chemin de drainage complet nécessite jusqu’à --session-stop-grace-sec + --drain-wait-sec + --post-session-hook-timeout-sec, plus 15 secondes de surcharge fixe pour le nettoyage des processus, plus 30 secondes supplémentaires lorsque --push-outcome-on-release est défini. Cela représente 80 secondes par défaut, et le runner enregistre le total au démarrage. Les sessions se drainent en parallèle sous ce budget unique, donc le total ne croît pas avec --capacity.
À la valeur par défaut --drain-wait-sec 0, un redémarrage continu interrompt les tours en cours ; chaque session reprend sur un autre runner, perdant le travail non poussé comme décrit sous Problèmes connus. Définissez --drain-wait-sec et augmentez la période de grâce pour correspondre, pour laisser les tours se terminer en premier.
Tout au long de ce chemin, le runner continue de faire un heartbeat vers le plan de contrôle à capacité zéro, de sorte que le bail de session n’expire pas et ne soit pas remis en file d’attente vers un autre runner tandis que le hook post-session écrit toujours le travail non validé. Le heartbeat s’arrête juste avant que le runner ne se désenregistre.
Donnez au runner au moins le total qu’il enregistre au démarrage avant que l’hôte ne l’arrête. L’endroit où vous définissez cela dépend de la façon dont vos hôtes s’arrêtent :
- Avec une période de grâce
SIGTERM: définissezterminationGracePeriodSecondssur Kubernetes,stop_grace_periodsur Docker Compose, ou l’équivalent de votre orchestrateur à au moins ce total. La valeur par défaut de Kubernetes de 30 secondes est plus courte que le chemin de drainage du runner, donc Kubernetes arrête le pod avant que le runner ne termine le drainage. - Avec
--retire-at: dimensionnez la marge entre l’heure de retraite et l’heure d’arrêt de l’hôte pour couvrir les tours typiques, plus la rétention des tâches de fond que Cycle de vie du runner décrit, plus ce même total. Calculez l’heure de retraite à chaque lancement, par exempledate +%splus la durée de vie prévue du runner. - Avec
--defer-shutdown-max-min: ajoutez deux parties supplémentaires au total du chemin de drainage. La première est les minutes que vous configurez. La seconde est la grâce post-libération que Différer le drainage au-delà du premier signal décrit, 75 secondes par défaut. Avec le flag défini, le runner imprime également la figure combinée au démarrage, après le total du chemin de drainage.
Différer le drainage au-delà du premier signal
Définissez--defer-shutdown-max-min <n> si vous souhaitez qu’un runner que vous redémarrez continue de servir les sessions qu’il détient pendant jusqu’à n minutes, au lieu de les drainer au premier signal. À la première SIGTERM ou SIGINT, le runner cesse de prendre de nouveaux travaux et continue de servir les sessions qu’il détient. Il continue de sonder pour que le plan de contrôle ne remette pas ces sessions en file d’attente. Nécessite Claude Code v2.1.238 ou ultérieur.
Ce qui arrive aux sessions que le runner détient après le premier signal
Dans les deux premiers stades qui suivent le signal, le runner libère les sessions, et une session libérée reprend sur un runner frais lorsque son utilisateur envoie son prochain message. En comptant à partir du premier signal, le runner passe par trois stades :- Pendant les premiers
nminutes : le runner sert ses sessions normalement et continue d’appliquer--startup-timeout-minet--kill-session-after-min. Si vous définissez également--release-idle-session-min, le runner libère toute session dont l’utilisateur a été inactif aussi longtemps ; sans cela, le runner ne libère aucune session tôt, à part un délai d’expiration au démarrage. - Lorsque les
nminutes s’écoulent : le runner libère chaque session qu’il détient toujours, inactive ou non. Le runner attend la fin du tour d’une session en cours de tour, et jusqu’à 60 secondes supplémentaires pour les tâches de fond d’un tour, avant de libérer cette session. - Lorsque la grâce post-libération s’écoule : le runner draine les sessions qu’il détient toujours, et le plan de contrôle remet chaque session drainée en file d’attente vers un autre runner immédiatement. La grâce post-libération commence lorsque les
nminutes s’écoulent et est de 75 secondes par défaut. Si vous définissez--drain-wait-secau-dessus de 60 secondes, la grâce post-libération est--drain-wait-secplus 15 secondes à la place.
--defer-shutdown-max-min. Une fois qu’un drainage est en cours, le signal suivant force la sortie du runner. Cela s’applique qu’un deuxième signal ou l’expiration de la grâce post-libération ait commencé le drainage.
Dimensionner le délai d’arrêt
Donnez à votre délai d’arrêt de l’hôte au moins la somme de trois parties : lesn minutes que vous configurez, la grâce post-libération, et le chemin de drainage complet que Délai d’arrêt décrit. Avec les paramètres par défaut, la grâce post-libération est de 75 secondes et le chemin de drainage est de 80 secondes, donc autorisez n minutes plus 155 secondes. Le runner imprime cette somme au démarrage chaque fois que --defer-shutdown-max-min est défini.
Si le délai d’arrêt s’écoule avant que le runner ne termine, l’hôte tue le runner. Les sessions qu’il détient toujours ne reçoivent aucun hook post-session. Le runner ne se désenregistre pas, et le plan de contrôle remet les sessions en file d’attente environ une minute plus tard. Si vous ne pouvez pas donner au délai d’arrêt cette somme, laissez --defer-shutdown-max-min non défini pour que le runner draine au premier signal à la place.
Ce qui atteint un hook post-session en cours d’exécution
Le hookpost-session et l’enfant de session Claude s’exécutent chacun dans leur propre groupe de processus POSIX, séparé de celui du runner, donc les mécanismes d’arrêt les atteignent différemment :
- Un
SIGTERMtandis que le runner draine déjà : force la sortie du runner immédiatement, en sautant tout ce qui reste du chemin de drainage. Sans--defer-shutdown-max-min, c’est le deuxièmeSIGTERMque le runner reçoit. Rien ne signale un hookpost-sessionen cours d’exécution, donc sur un hôte nu où un processus init adopte les orphelins, il se termine de lui-même, mais sans supervision : son budget de délai d’expiration ne s’applique plus, et une écriture au tuyau de journal fermé peut le tuer avecSIGPIPE, donc un hook qui doit survivre à une sortie forcée là devrait rediriger sa propre sortie vers un fichier. Dans les recettes de conteneur sur cette page, le runner est le PID 1 du conteneur et sa sortie termine le conteneur, et sous leKillMode=control-grouppar défaut de systemd, la suppression au niveau du cgroup atteint également le hook, comme l’entrée Suppressions au niveau du cgroup le décrit ; dans les deux cas, traitez une sortie forcée comme fatale au hook et fiez-vous à la période de grâce à la place. - Signaux au niveau du groupe de processus, tels que
kill -- -<pid>dans un script wrapper, le contrôle des tâches du shell, ou un watchdog au niveau du groupe : atteignent le runner et un sous-processus de hookcheckouten cours, qui reste intentionnellement attaché au groupe, mais pas un hookpost-sessionen cours d’exécution ou l’enfant de session. - Suppressions au niveau du cgroup, telles que le
KillMode=control-grouppar défaut de systemd ou leSIGKILLque Kubernetes livre à tout le conteneur lorsqueterminationGracePeriodSecondsexpire : atteignent tout, y compris le hook. L’isolation du groupe de processus ne protège pas contre celles-ci, c’est pourquoi la période de grâce doit couvrir le chemin de drainage complet. - Le délai d’expiration du hook lui-même : lorsqu’un hook dépasse
--post-session-hook-timeout-sec, le runner envoieSIGTERMà tout le groupe de processus du hook, puisSIGKILLdeux secondes plus tard, donc un worker que le hook a forké, tel que tar, rsync, ou git, se termine avec le shell wrapper au lieu de survivre en tant qu’orphelin. La supervision du runner se termine une fois que le stdio du hook se ferme : un worker qui a redirigé sa propre sortie vers un fichier et survit à l’étapeSIGTERMest au-delà de la portée du runner.
post-session s’exécutent toujours, afin que vous puissiez distinguer un drainage silencieux d’un qui est en cours de snapshot.
Garder le répertoire de base et la capacité identiques sur tous les runners
Si un runner meurt en milieu de session, le serveur remet la session en file d’attente et un autre runner dans l’environnement la récupère. Ce runner dérive le chemin de checkout de ses propres--base-dir et --capacity : --capacity 1 se vérifie directement sous --base-dir, et un --capacity au-dessus de 1 utilise des worktrees par session à la place. Quand les runners dans le même environnement utilisent des valeurs différentes pour l’un ou l’autre drapeau, le répertoire de travail de la session reprise change, et les chemins absolus que l’agent a enregistrés plus tôt, dans les éditions, les appels d’outils, ou ses propres notes, pointent vers un emplacement qui n’existe plus.
Utilisez le même --base-dir et --capacity sur chaque runner dans un environnement, et n’utilisez pas une valeur par hôte comme un ID d’instance ou un nom d’hôte.
Le répertoire de base par défaut est /workspace, avec l’exception que la ligne de référence --base-dir enregistre. Le runner a besoin d’accès en écriture à celui-ci. Au démarrage, avant de s’enregistrer, le runner crée le répertoire et confirme qu’il peut y écrire, et quitte avec cannot create or write to base directory quand il ne peut pas. Un runner démarré en tant que root crée le /workspace par défaut lui-même. Pour un runner non-root, créez le répertoire et donnez la propriété à l’utilisateur du runner avant de démarrer le runner, ou pointez --base-dir vers un répertoire que cet utilisateur possède déjà.
Réutiliser un checkout pré-chauffé
Pour les grands référentiels, le clone peut dominer le démarrage de la session. À--capacity 1 sans hook checkout, le runner garde un clone canonique par référentiel à <base-dir>/<repo-owner>/<repo> et le réutilise entre les sessions : il récupère la ref demandée, détache HEAD, et réinitialise dur à celle-ci, ce qui est quasi-instantané quand peu a changé. Pour sauter le clone froid, fournissez le clone de l’une de deux façons :
- Clone dans l’image : construisez le clone dans votre image runner à ce chemin. Chaque conteneur frais démarre alors avec le clone chaud sans réutiliser un disque.
- Clone sur un volume persistant : sur les runners que vous pré-verrouillez au compte d’un utilisateur avec
--lock-to-account, pointez--base-dirvers un volume persistant, pour que le disque ne serve que ce compte. Un runner pré-verrouillé ne récupère jamais les sessions de canal Claude Tag, donc cette option ne s’applique pas aux runners qui les servent.
- N’importe quelle forme de clone fonctionne : un clone complet, peu profond, ou à branche unique au chemin est utilisé tel quel. Le runner ne passe jamais
--depthlors de la récupération dans un clone existant, donc un pré-chauffage complet garde son historique complet et un peu profond reste peu profond.CLAUDE_RUNNER_FETCH_DEPTH(full,0, ou un nombre ; par défaut 50) contrôle uniquement le clone froid que le runner fait quand aucun clone n’existe encore. - Les changements suivis se réinitialisent, les fichiers non suivis persistent : chaque session commence à partir d’une réinitialisation dur qui efface les modifications suivies de la session précédente, mais le runner ne lance jamais
git clean, donc les fichiers non suivis des sessions antérieures du propriétaire verrouillé restent dans l’arbre. - Avec le proxy git, la réinitialisation devient un checkout : avec
--use-anthropic-git-proxy, le runner assainit le.git/du clone avant chaque session, gardant le magasin d’objets, les refs, et l’état peu profond mais supprimant l’index, donc chaque session paie un checkout complet de l’arbre de travail au lieu d’une réinitialisation quasi-instantanée ; il ne re-clone toujours pas. Les pré-chauffages de sous-module ne sont pas supportés sous le proxy. - Les longs clones n’ont besoin d’aucune solution de contournement : le runner limite chaque opération git avec un watchdog sans progrès de 120 secondes et un plafond dur de 30 minutes, pas un délai d’expiration plat, donc un clone froid lent qui continue de signaler le progrès se termine.
Épingler la version
Le processus enfant Claude Code de chaque session exécute le binaire du runner lui-même, et le runner désactive la mise à jour automatique à l’intérieur des sessions qu’il génère, donc chaque session exécute la version que vous avez installée sur l’hôte ou construite dans l’image. Une mise à jour au niveau de l’hôte prend effet la prochaine fois que le runner démarre.- Pour garder une flotte sur une version : construisez l’image avec une version épinglée, ou sur un hôte nu installez une version spécifique et désactivez les mises à jour automatiques
- Pour mettre à niveau : installez la version plus récente ou reconstruisez l’image, puis redémarrez les runners
- Plugins : les places de marché de plugins ne se mettent pas à jour automatiquement non plus ; définissez
FORCE_AUTOUPDATE_PLUGINS=1dans l’environnement du runner pour laisser les plugins se mettre à jour automatiquement pendant que le binaire reste épinglé
Mettre à l’échelle la flotte
Votre orchestrateur décide quand ajouter ou supprimer des runners. En raison du verrou d’un propriétaire par runner, le nombre minimum de répliques est le nombre d’utilisateurs et d’agents Claude Tag que vous vous attendez à être actifs simultanément ;--capacity contrôle le parallélisme au sein des sessions d’un propriétaire, pas entre les propriétaires.
Deux approches de mise à l’échelle sont disponibles :
- Flotte fixe : exécutez un ensemble statique de répliques runner et mettez à l’échelle sur les métriques Prometheus que chaque runner sert
- Runners à la demande : exécutez la sous-commande
claude self-hosted-runner orchestrator, qui interroge Anthropic pour les sessions en attente sans runner disponible et invoque votre hookspawn-runnerpour en démarrer un par session. Consultez Runners à la demande.
Problèmes connus et limitations
Voici les limitations de cette version, avec des solutions de contournement où l’une existe.Le trafic du connecteur quitte votre réseau
Anthropic appelle les outils connecteur, tels que GitHub, Slack, Linear, et les autres connecteurs claude.ai, à partir de sa propre infrastructure plutôt que de votre runner, donc quand Claude utilise un connecteur dans une session auto-hébergée, ce trafic passe parapi.anthropic.com plutôt que d’originer à l’intérieur de votre limite réseau. Pour garder un connecteur hors des sessions auto-hébergées, filtrez-le comme n’importe quel autre serveur MCP avec les paramètres de politique allowedMcpServers et deniedMcpServers. Claude Code applique ces paramètres aux connecteurs qu’Anthropic livre ainsi qu’aux serveurs que vous configurez, donc si vous déployez une liste d’autorisation pour d’autres serveurs, Claude Code bloque également les connecteurs livrés. Pour garder les connecteurs disponibles aux côtés d’une liste d’autorisation basée sur URL, ajoutez des entrées qui correspondent aux chemins de proxy Anthropic pour les connecteurs livrés :
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
Certaines sessions ne comptent pas comme inactives
Une session tenant une tâche de fond qui ne se termine jamais ne compte pas comme inactive, donc--release-idle-session-min ne libérera pas l’emplacement de cette session. Une session qui attend une approbation demandée de l’intérieur d’un appel d’outil en cours d’exécution ne compte pas non plus comme inactive. Définissez toujours --kill-session-after-min à côté comme un arrêt dur pour qu’aucune session ne puisse tenir un emplacement indéfiniment.
--kill-session-after-min est un arrêt dur pour les sessions qui s’échappent. Le runner termine toute session qui atteint la limite, même une que quelqu’un utilise toujours, donc définissez le drapeau bien au-dessus de votre session la plus longue attendue, comme --kill-session-after-min 480 pour 8 heures. Pour libérer les emplacements des conversations qui deviennent inactives, utilisez --release-idle-session-min à la place.
Limitations supplémentaires
- Les sessions reprises perdent le travail non poussé : quand une session est libérée, sur un délai d’expiration d’inactivité ou sur un redémarrage du runner, et que l’utilisateur envoie un autre message, la session reprend sur un runner frais qui clone le référentiel à nouveau à partir de sa branche de démarrage, donc le travail que la session n’avait pas poussé est parti. Définissez
--push-outcome-on-releasepour que le runner fasse un meilleur effort de push des branches de résultat de la session avant de la libérer, pour que la session reprise commence à partir de ces commits à la place ; cela préserve le travail engagé, pas un arbre de travail sale. Avant de l’activer, limitez qui peut pousser vers les refsclaude/*sur la télécommande source, par exemple avec une règle de branche : à la reprise, le runner récupère la branche précédemment poussée sans vérifier qui l’a poussée, donc n’importe qui avec accès push à ces refs peut placer du contenu dans l’espace de travail repris. Le runner rejette également la configuration par session à la reprise, ce qui signifie le répertoire de configuration Claude de la session et tout état de shell que la session a écrit ;--push-outcome-on-releasene couvre pas ceux-ci. - Les référentiels privés ne peuvent pas être ajoutés en milieu de session : un référentiel ajouté à une session après son démarrage n’est pas cloné avec des identifiants sur un runner auto-hébergé, donc l’ajout échoue. Sélectionnez chaque référentiel dont la session a besoin quand vous la créez.
- Certains connecteurs n’apparaissent pas dans les sessions auto-hébergées : un connecteur que vous n’avez pas encore connecté dans les paramètres claude.ai n’est pas listé dans une session auto-hébergée, et la session ne vous invitera pas à le connecter. Connectez-le d’abord dans les paramètres, puis démarrez une session fraîche. Ajouter un connecteur à une session déjà en cours d’exécution ne rend pas non plus ses outils disponibles à Claude ; démarrez une session fraîche pour récupérer un connecteur nouvellement ajouté.
Signaler un problème
Pour les problèmes avec les environnements auto-hébergés, contactez votre équipe de compte Anthropic.Dépannage
Pour un diagnostic guidé, exécutez la sous-commande doctor sur l’hôte runner. La sous-commande doctor démarre une session Claude Code interactive avec les journaux et l’état du runner attachés. Connectez-vous d’abord avecclaude auth login sur cet hôte pour que la session puisse interroger votre environnement, ses runners, et ses sessions en attente. Sans cette connexion, par exemple quand l’hôte s’authentifie avec une clé API, il est limité au point de terminaison de santé local, aux métriques, et au journal du runner, et il lit le journal uniquement si vous avez démarré le runner avec --log-file.
- Le runner n’apparaît pas dans l’environnement : confirmez que l’hôte peut atteindre
api.anthropic.comsur HTTPS, que le secret de l’environnement est actuel, et que l’horloge de l’hôte est à moins de cinq minutes de l’heure réelle ; un décalage plus grand cause l’échec de l’authentification. Le runner enregistre[runner:fatal]avec la raison du rejet en cas d’échec d’authentification. - Le runner quitte au démarrage avec
cannot create or write to base directory: le runner ne peut pas créer ou écrire à--base-dir, qui par défaut est/workspace. Corrigez la propriété du répertoire ou pointez--base-dirvers un chemin inscriptible, comme décrit dans Garder le répertoire de base et la capacité identiques sur tous les runners. Si le runner enregistre à la place[runner:fatal]disant que la vérification du répertoire de base a expiré, le répertoire est sur un montage NFS ou CSI suspendu. Vérifiez la santé du montage plutôt que les permissions. Le runner imprime ces deux échecs de démarrage à stderr avant d’ouvrir--log-file, donc cherchez-les dans le terminal ou les journaux de conteneur de votre plateforme plutôt que dans le fichier journal. Avant v2.1.225, le runner ne vérifiait pas le répertoire de base au démarrage, et cette mauvaise configuration échouait les sessions après la récupération à la place. - Les sessions restent en attente : chaque runner en ligne peut être verrouillé à un propriétaire différent. Vérifiez la métrique
claude_code_self_hosted_runner_locked_accountde chaque runner métrique ou le champlocked_accountde sa ligne de journal[runner:health]pour voir qui la détient. Les deux affichent l’email du propriétaire uniquement après que le runner ait reçu un jeton de session portant une réclamationact.email, ce qu’une session d’agent Claude Tag ne fait jamais. Sans la réclamation, le runner n’émet aucune sérielocked_accountet enregistrelocked_account=yes, ce qui vous dit que le runner est verrouillé mais pas à quel propriétaire. Ajoutez des répliques, ou attendez qu’un runner existant se draine et redémarre. Si l’environnement utilise des runners à la demande, vérifiez l’orchestrateur à la place ; consultez Runners à la demande. - Les sessions échouent immédiatement après la récupération : ouvrez la session dans claude.ai/code pour voir l’erreur. Les causes les plus courantes sont les identifiants git manquants identifiants git dans l’image runner et les outils de build qui ne sont pas installés. Un répertoire de base non inscriptible arrête le runner au démarrage au lieu d’échouer les sessions. Consultez l’entrée Le runner quitte au démarrage avec
cannot create or write to base directorydans cette liste. - Les sessions ne peuvent pas atteindre le réseau via un proxy de sortie authentifiant : quand la source que vous avez définie avec
--proxy-authorization-commandou--proxy-authorization-fileéchoue, expire après 30 secondes, ou produit une valeur vide, le runner répond à cette connexion502 Bad Gatewayet enregistre pourquoi. Le runner rédige la stderr de la commande dans ce journal et ne journalise jamais la valeur de l’en-tête. Avec--proxy-authorization-command, exécutez la commande vous-même sur l’hôte pour confirmer qu’elle imprime la valeur d’en-tête entière sur stdout. Si le runner quitte à la place au démarrage aveccould not start the proxy-authorization listener, il ne pouvait pas ouvrir son écouteur de boucle locale. - Le runner enregistre des lignes
Poll failedcontenantrejecting the malformed poll response: le runner a reçu une réponse de sondage dont le corps n’est pas le JSON attendu de la file d’attente, le plus souvent parce que quelque chose entre le runner etapi.anthropic.com, comme un proxy d’interception ou un portail captif, a répondu avec sa propre page. Le runner rejette la réponse, la compte sous le typetransportde la métriqueclaude_code_self_hosted_runner_poll_errors_total, et réessaie selon le calendrier de sondage échoué décrit dans Cycle de vie de la session. Le runner continue de servir ses sessions en direct. Configurez le proxy pour passer les réponses deapi.anthropic.cominchangées. Avant v2.1.246, le runner lisait une telle réponse comme une file d’attente de travail vide, ce qui pouvait terminer ses sessions en direct ou le faire quitter. - La branche d’une session n’existe plus sur la télécommande : pour une source git que la session ne lit que, le runner saute cette source et continue sur les autres. Pour la source vers laquelle la session pousse les résultats, une branche supprimée, généralement parce qu’elle a été fusionnée et supprimée automatiquement, échoue la session avec une erreur nommant le référentiel et la branche et vous demandant de restaurer la branche et de réessayer. Le runner échoue la session avec la même erreur quand sauter laisserait sans référentiel du tout. Avant v2.1.228, une telle session démarrait dans un répertoire vide.
- Les sessions prennent des minutes pour démarrer : le clone initial domine généralement. Regardez la métrique
claude_code_self_hosted_runner_session_init_duration_secondspour confirmer, et coupez le clone avec un checkout pré-chauffé ou unCLAUDE_RUNNER_FETCH_DEPTHplus petit. - Le pod est tué en milieu de drainage : augmentez
terminationGracePeriodSecondsà au moins la valeur que le runner enregistre au démarrage. Consultez Timing d’arrêt.
[runner:fatal], à stdout, et la sortie de débogage à stderr, tout comme des lignes en texte brut plutôt que JSON. Les échecs de démarrage décrits dans les entrées de dépannage ci-dessus s’impriment à stderr avant ce point. Capturez les deux flux avec --log-file, ce qui permet également à self-hosted-runner doctor de les suivre, ou avec la collecte de journaux de votre plateforme. Le processus enfant de chaque session écrit un journal de débogage séparé. En cas d’échec, le runner préserve le journal, imprime le chemin du journal dans le journal du runner, et affiche la queue du journal aux côtés de la session dans claude.ai/code.
Prochaines étapes
- Personnaliser les sessions : scripts wrapper, hooks de cycle de vie, runners à la demande, serveurs MCP, et permissions
- Tester de bout en bout : vérifier une nouvelle image runner à partir de CI avant de la promouvoir
- Référence : chaque drapeau CLI, variable d’environnement, et métrique