Les environnements auto-hébergés sont en bêta publique sur les plans Team et Enterprise ; un Owner les active en activant Allow self-hosted environments sur la page d’administration Cloud environments. Cette page suppose un runner fonctionnel ; consultez le guide de démarrage rapide pour la configuration et Déployer en production pour les recettes de flotte.
pool, comme CLAUDE_RUNNER_POOL_ID ; les noms de flag CLI et de variable d’environnement utilisent environment, comme --environment-secret-file.
Scripts wrapper
Utilisez un script wrapper quand chaque session a besoin d’une configuration que le runner ne peut pas faire seul : provisionner des identifiants de courte durée limités au créateur de la session, exporter des secrets spécifiques à l’environnement, préparer des chaînes d’outils de langage ou appliquer des limites de ressources autour du processus enfant. Le runner démarre votre wrapper à la place du binaire Claude Code, une fois par session. Terminez le wrapper enexec-ant dans $CLAUDE_RUNNER_CLAUDE_BIN, le binaire du runner lui-même, afin que les signaux et les codes de sortie se propagent correctement.
Pointez --exec-path, ou SELF_HOSTED_RUNNER_EXEC_PATH, vers le wrapper quand vous démarrez le runner :
Le wrapper hérite également du reste de l’environnement géré de l’enfant, y compris toutes les variables d’environnement fournies par le serveur.
exec propage tout automatiquement ; si votre wrapper lance l’enfant d’une autre manière, transmettez l’environnement complet.
Vous pouvez lire les variables CLAUDE_CODE_REMOTE_SLACK_* dans votre wrapper ou votre hook command, ainsi que dans ce que la session exécute, comme les commandes shell, les hooks git et les hooks Claude Code. Les hooks checkout, post-session et spawn-runner ne les reçoivent pas.
Donner une valeur par défaut aux variables qui peuvent être non définies
Si votre script utiliseset -u, développez CCR_SESSION_ACCOUNT_EMAIL, CLAUDE_RUNNER_CLIENT_PLATFORM, CLAUDE_CODE_REMOTE_SLACK_THREAD_URL, CLAUDE_CODE_REMOTE_SLACK_THREAD_TS et CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID avec une valeur par défaut, comme "${CCR_SESSION_ACCOUNT_EMAIL:-}". Chacune peut être non définie, et avec set -u, Bash s’arrête avec unbound variable lorsqu’il développe une variable non définie.
Mettre entre guillemets les arguments et les variables que votre script développe
Mettez entre guillemets chaque argument et chaque variable que le runner transmet à votre wrapper ou à votre hookcommand. Certaines valeurs contiennent des caractères qu’un shell interprète, car elles contiennent du JSON, des phrases entières ou une règle de permission comme Bash(npm run test *).
- Arguments : transmettez-les sous la forme
"$@" - Variables : mettez chacune d’elles entre guillemets, comme dans
"${CLAUDE_CODE_REMOTE_SLACK_THREAD_URL:-}". Le lien du fil Slack, par exemple, peut contenir?et&. - Chaînes
evaletsh -c: ne substituez pas une valeur dans une chaîne queevaloush -cexécute, même entre guillemets. Faites plutôt référencer la variable par cette chaîne.
Garder stdin et le descripteur de fichier 3 attachés
stdin de l’enfant est le canal de contrôle du runner. Les rotations de token et les signaux de fin de session arrivent dessus. Le runner ouvre également un tuyau sur le descripteur de fichier 3 et lit les signaux d’activité de l’enfant à partir de celui-ci pour piloter les délais d’inactivité et de démarrage. Un simpleexec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" préserve les deux automatiquement.
Si votre wrapper met l’enfant en arrière-plan avec un simple &, il coupe stdin de l’enfant. La session semble saine jusqu’à ce que la durée de vie du token OAuth initial d’environ 30 minutes expire, puis chaque appel API qui utilise le token échoue avec 401 authentication_error. Si votre wrapper doit mettre l’enfant en arrière-plan, par exemple pour garder un trap de démontage actif, enregistrez stdin sur le descripteur de fichier 9 et réattachez-le explicitement :
- Descripteur de fichier 3 : transporte les signaux d’activité de l’enfant vers le runner. Ne le fermez pas et ne le réutilisez pas dans le wrapper.
- Descripteurs de fichier 4 et 5 : les runners sur Claude Code v2.1.309 ou ultérieur les réservent à l’enfant. Ne les fermez pas et ne les réutilisez pas dans le wrapper. Si votre wrapper utilise l’un d’eux, par exemple pour enregistrer stdin, déplacez cet usage vers un descripteur de 6 à 9, que les versions antérieures laissent également libres.
- stderr : lorsque le wrapper ou l’enfant se termine avec un code non nul, le runner publie les dernières lignes de stderr dans la session et les affiche dans son propre log. L’utilisateur de la session voit ces lignes, donc n’affichez pas de secrets sur stderr, et retirez
set -xavant de déployer le wrapper. Si vous redirigez stderr, les sessions s’exécutent toujours, mais le runner signale un échec avec le seul code de sortie.
Transmettre les flags de prompt système
Le prompt système et le prompt système ajouté que le plan de contrôle d’Anthropic envoie pour une session parviennent à votre wrapper sous forme de chemins de fichiers, et non de texte en ligne. Le runner écrit chaque prompt dans un fichier du répertoire de configuration de la session,CLAUDE_CONFIG_DIR, et transmet son chemin dans les arguments que reçoit votre wrapper, sous la forme --system-prompt-file <path> ou --append-system-prompt-file <path>.
Les runners sur Claude Code v2.1.281 ou ultérieur livrent les prompts sous forme de fichiers. Avant la v2.1.281, le runner les transmettait sous la forme --system-prompt <text> et --append-system-prompt <text>.
Dans votre script wrapper ou votre hook command, gérez ces flags comme suit :
- Transmettez-les : terminez le wrapper avec
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@", qui transmet les flags de fichier avec tous les autres arguments. Ne les supprimez pas et ne les réécrivez pas. Si une session perd un flag de fichier de prompt, elle s’exécute sans les instructions que le plan de contrôle lui a envoyées. - Sur un runner en v2.1.281 ou ultérieur, un flag de fichier que vous ajoutez remplace celui du serveur, sans jamais s’y ajouter : chaque flag de fichier de prompt n’accepte qu’une seule valeur et Claude Code conserve la dernière occurrence, donc si vous ajoutez
--append-system-prompt-file <path>après"$@", le contenu de votre fichier remplace les instructions ajoutées par le serveur. Pour ajouter des instructions en plus de celles du serveur, placez-les dans leCLAUDE.mdde l’image du runner, que le runner injecte dans la configuration de niveau utilisateur de chaque session.
Corriger Argument list too long au démarrage de la session
Sur Linux, si vous réunissez le prompt du plan de contrôle et votre propre texte dans un seul argument --append-system-prompt, le runner consigne une ligne comme celle-ci dès que l’argument atteint 128 Ko :
getconf ARG_MAX, souvent 2 Mo, est une limite distincte qui porte sur l’ensemble des arguments et de l’environnement.
Sur Claude Code v2.1.283 ou ultérieur, laissez les flags de fichier dans "$@" et transmettez uniquement votre propre texte avec --append-system-prompt. Claude Code utilise les deux, d’abord le prompt du plan de contrôle, puis votre propre texte :
CLAUDE.md de l’image du runner, car il reste un seul argument.
En v2.1.281 ou v2.1.282, cette commande se termine avec Cannot use both --append-system-prompt and --append-system-prompt-file. Sur les versions antérieures, votre texte remplace le prompt du plan de contrôle sans erreur. Dans tous ces cas, effectuez d’abord la mise à jour, ou déplacez votre propre texte dans CLAUDE.md.
Provisionner les identifiants limités au créateur de la session
Utilisez la sous-commandedecode-token pour lire les revendications du JWT de session. Elle lit le token à partir d’un argument, de CLAUDE_CODE_SESSION_ACCESS_TOKEN ou de stdin, dans cet ordre ; voir Vérifier le token à l’intérieur de la session pour ce qu’elle vérifie. L’exemple ci-dessous décode l’identité du créateur, l’échange contre des identifiants AWS de courte durée et exec dans Claude Code :
jq -re plutôt que jq -r quand la revendication extraite contrôle une décision d’authentification, afin qu’une revendication absente se termine avec un code non-zéro au lieu de transmettre la chaîne littérale null en aval. Les sessions créées par une identité de service d’organisation, comme les sessions de bot et d’agent, portent un sujet agent: plutôt que user:, donc cet exemple les refuse ; si votre environnement sert ces sessions, décidez explicitement si le wrapper revient à un identifiant par défaut pour elles au lieu de se terminer. Quand votre échange d’identifiants a besoin de l’email à la place, lisez .act.email et gérez son absence : le token ne le porte que quand la surface de création l’a enregistré, et une session envoyée par CLI peut ne pas l’avoir. Pour la référence complète des revendications et la vérification à partir de services en dehors du runner, voir Vérifier l’identité de la session.
Hooks de cycle de vie
Les hooks de cycle de vie remplacent les étapes du pipeline par session du runner par vos propres scripts. Pointez le runner vers un répertoire de hooks avec--hooks-dir <path>, ou SELF_HOSTED_RUNNER_HOOKS_DIR. Le runner cherche des fichiers exécutables avec des noms bien connus ; tout hook qui n’est pas présent revient au comportement intégré, donc vous n’écrivez que ceux dont vous avez besoin. Les hooks s’exécutent avec les privilèges du runner lui-même, et les enfants de session partagent cet UID, donc montez le répertoire de hooks en lecture seule, ou intégrez-le à l’image, afin que le code de session ne puisse pas le modifier ; voir la section de durcissement.
Ces hooks sont distincts des hooks Claude Code, qui s’exécutent à l’intérieur de la session ; les hooks de cycle de vie s’exécutent sur le runner, autour de la session.
checkout
S’exécute une fois par dépôt, à la place du clone et de la récupération intégrés du runner. Utilisez le hook pour cloner à partir d’un miroir de lecture directe accessible via HTTPS ou SSH, amorcer un arbre de travail à partir d’une archive ou appliquer une authentification git par session. Le runner définit ces variables, et peut définir d’autres variablesCLAUDE_RUNNER_ que le tableau ne répertorie pas :
Le script doit laisser un arbre de travail à
CLAUDE_RUNNER_CHECKOUT_PATH extrait à la révision demandée. Un HEAD détaché convient, car le runner crée la branche de travail de la session par-dessus.
Après le retour de votre hook, le runner vérifie que CLAUDE_RUNNER_CHECKOUT_PATH contient un .git. Si votre hook matérialise une source non-git comme Perforce ou une archive tarball dépaquetée, définissez CLAUDE_RUNNER_SKIP_GIT_VERIFY=1 dans l’environnement du runner pour ignorer cette vérification. Les flux basés sur git comme la création de branche de travail et l’envoi des résultats nécessitent un checkout git, donc exportez les résultats des arbres non-git avec un hook post-session.
Obtenir des identifiants git dans le hook
Le runner ne transmet pas d’identifiants git au hook. La sous-commandedecode-token n’est pas non plus disponible ici, car CLAUDE_RUNNER_CLAUDE_BIN n’est pas défini dans l’environnement du hook de checkout. Générez plutôt des identifiants de clone par session à partir de l’identité de la session, ou revenez à l’authentification git propre à l’hôte :
- Identifiants de clone par session : vérifiez
CLAUDE_CODE_SESSION_ACCESS_TOKENavec une bibliothèque JWT standard par rapport à l’endpoint JWKS sousCLAUDE_RUNNER_API_BASE_URL, comme décrit dans Vérifier le token à partir de votre service. Faites ensuite émettre par votre service d’identifiants des identifiants de clone de courte durée pour l’identité figurant dans la revendicationactdu jeton. Associez ces identifiants àact.sub, et n’exigez pasact.email. - Authentification git de l’hôte : utilisez l’authentification git dont l’hôte dispose déjà, comme un agent SSH, un helper d’identifiants ou
.netrc.
Quand le hook échoue
Le hook échoue lorsqu’il se termine avec un code non nul, ou se termine avec 0 sans laisser de checkout utilisable :-
Un dépôt vers lequel la session envoie les résultats : le runner fait échouer la session et, en cas de sortie non nulle, affiche à l’utilisateur la fin du stderr du script. Écrivez la raison dans stderr sur une seule ligne courte, écrivez-la en dernier, puis terminez avec un code non nul. N’affichez pas de secrets dans stderr, et supprimez
set -xavant de déployer le hook. Si le message gitcouldn't find remote refapparaît dans environ les 4 derniers Ko du stderr du hook, le runner signale plutôt une branche supprimée. Le message d’erreur de la session indique alors que la branche n’existe plus sur le remote et demande à l’utilisateur de la restaurer. Si cette branche existe, voir Une session échoue avecno longer exists on the remoteoucouldn't find remote ref. -
Un dépôt que la session lit uniquement, comme un dépôt ajouté à une session en cours d’exécution : le runner l’ignore et continue avec les dépôts restants. Le runner publie une étape
Skippeddans la session et supprime ce que le hook a laissé au chemin de checkout. Lorsque l’étapeSkippedmentionne le hook de checkout, trouvez le détail de l’échec sur la ligne[runner:warn] checkout hook faileddans le log du runner. Lorsque l’étape indique qu’une ref sourceno longer exists on remote, voir Une session échoue avecno longer exists on the remoteoucouldn't find remote ref. Si l’omission laisse la session sans aucun dépôt, le runner fait quand même échouer la session.
post-session
S’exécute une fois par session, après la sortie de l’enfant Claude Code et avant que le runner ne démonte l’espace de travail. Ce hook est votre seule chance de sauvegarder le travail non commis : à--capacity au-dessus d’un, le runner supprime les worktrees par session juste après le retour du hook, et à --capacity 1 le clone canonique réutilisé est réinitialisé en dur quand la session suivante démarre, donc les changements suivis non commis ne survivent sur aucun chemin. Les utilisations typiques sont l’envoi d’une branche d’instantané de changements non commis, l’archivage de journaux ou l’émission d’un événement de fin de session à vos propres systèmes.
Le hook se déclenche à chaque fin de session où un processus enfant a été généré, quelle qu’en soit la cause ; les valeurs CLAUDE_RUNNER_EXIT_REASON ci-dessous énumèrent les cas. Il ne peut pas se déclencher quand le runner se termine brutalement, comme une préemption de VM ou une perte de courant ; si vous avez besoin de garanties contre une terminaison brutale, prenez des instantanés périodiquement à partir de l’intérieur de la session avec un hook Claude Code PostToolUse à la place. Le runner définit :
CLAUDE_RUNNER_EXIT_REASON prend l’une des quatre valeurs :
completed: la session s’est terminée proprement. Le processus Claude Code s’est terminé normalement, ou s’est terminé de lui-même après l’archivage ou la suppression de la session.failed: le processus Claude Code s’est écrasé, ou la configuration a échoué après son démarrage.interrupted: le runner a arrêté la session, dans l’un des cas suivants :- Le runner a libéré la session pour libérer l’emplacement.
- La session a expiré au démarrage.
- Le serveur a déplacé la session hors de ce runner.
- L’interrogation du runner a détecté un archivage ou une suppression avant la sortie du processus.
- Le runner était en drainage.
- La session a dépassé sa limite
--kill-session-after-min.
abandoned: réservé à une session qu’un autre runner a revendiquée. Le hook ne se déclenche actuellement pas dans ce cas.
interrupted y soient comptés comme completed. Les compteurs comptent comme completed une libération, un délai d’expiration au démarrage, un déplacement du serveur, ainsi qu’un archivage ou une suppression détectés en premier par l’interrogation du runner, car le runner a remis l’emplacement proprement.
Le statut de sortie du hook n’affecte jamais le résultat de la session ; un échec est enregistré et ignoré. Le runner attend jusqu’à --post-session-hook-timeout-sec, 60 secondes par défaut, à chaque fin de session y compris l’arrêt du runner. Cet exemple sauvegarde le travail non commis dans une branche de secours :
GIT_ALLOW_PROTOCOL du script limite git aux remotes HTTPS, HTTP et SSH. Si l’environnement du runner définit déjà sa propre liste GIT_ALLOW_PROTOCOL non vide, le script conserve cette liste.
Le hook envoie avec les identifiants git disponibles dans son propre environnement sur l’hôte du runner. Sous la posture sans identifiants dans l’image, y compris quand le clone intégré passe par le proxy git Anthropic, il n’y en a pas, donc générez des identifiants de push de courte durée à l’intérieur du hook avant d’envoyer : échangez le jeton de session que le hook reçoit dans CLAUDE_CODE_SESSION_ACCESS_TOKEN avec votre propre service de jetons, en le vérifiant comme Vérifier l’identité de la session le décrit.
Traitez tous les identifiants que votre hook fournit à git comme des identifiants qu’une session peut obtenir, et générez-les de sorte qu’ils ne puissent rien faire de plus que ce push. Git dans votre hook lit des fichiers de configuration qu’une session peut écrire, et un helper d’identifiants ou un filter driver désigné dans l’un d’eux s’exécute avec les privilèges de votre hook. Les paramètres de ces fichiers peuvent aussi modifier la destination d’un push, quel que soit le remote que vous indiquez. Pour les paramètres git que le runner fixe dans votre hook et ceux qu’il laisse à ces fichiers, voir Configuration git dans les hooks de cycle de vie.
Timing du hook quand le runner libère une session
Une session libérée peut reprendre sur un autre runner. Sur un runner en v2.1.236 ou ultérieur, ce que la session faisait à la libération décide si elle peut reprendre avant que ce hook se termine :- Inactif après un tour, ou expiré au démarrage : le runner arrête l’enfant et exécute ce hook jusqu’à la fin. Ce n’est qu’alors qu’il libère la session. Un message utilisateur envoyé pendant que le hook s’exécute ne peut pas reprendre la session sur un autre runner avant que le hook se termine.
- En attente que l’utilisateur réponde à une invite, comme une invite de permission : le runner libère d’abord la session, puis exécute ce hook. Un message utilisateur envoyé pendant que le hook s’exécute peut reprendre la session sur un autre runner avant que le hook se termine.
--retire-at, et, sur un runner en v2.1.260 ou ultérieur, à la limite --kill-session-after-min d’une session. Une session dont le tour s’est terminé et qui ne contient que des tâches en arrière-plan compte comme inactif ici. Avant v2.1.236, le runner libérait d’abord la session puis exécutait ce hook dans les deux cas.
Pendant un drainage SIGTERM, le runner maintient le bail de session jusqu’à ce que le hook se termine ; voir Timing d’arrêt.
Configuration git dans les hooks de cycle de vie
Les hookscheckout et post-session s’exécutent avec le jeton d’accès de la session dans leur environnement, et le git qu’ils exécutent lit des fichiers de configuration que les sessions peuvent écrire, comme ~/.gitconfig et le .git/config d’un checkout. Avant l’exécution de l’un ou l’autre hook, le runner définit des paramètres git dans l’environnement du hook, dont ceux ci-dessous, sous forme de paires GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n et de variables d’environnement git. Git les classe au-dessus de tout fichier de configuration, et ils s’appliquent uniquement au git que vos hooks exécutent, pas au git de la session elle-même. Au démarrage, le runner affiche une ligne [runner:git] lifecycle hooks: qui indique le chemin des hooks, les protocoles autorisés, les programmes gpg et le mode de signature en vigueur. Nécessite Claude Code v2.1.280 ou ultérieur.
- Hooks git : sauf si vous fournissez une valeur,
core.hooksPathvaut/dev/null, donc git ignore les hooks du.git/hooksd’un dépôt et tout répertoire de hooks que~/.gitconfigdésigne. Pour en fournir une, exportezcore.hooksPathsous forme de paireGIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_ndans l’environnement du runner. Le runner lit aussicore.hooksPathdepuis la configuration git système, et ne l’utilise que lorsque l’utilisateur du runner ne peut écrire ni ce fichier, ni le répertoire qu’il désigne, ni les fichiers de hooks qu’il contient. Quand le runner ignore une valeur, une ligne[runner:warn]au démarrage indique la valeur et la raison. - Moniteur du système de fichiers :
core.fsmonitorest vide, donc git dans votre hook n’exécute pas de programme de surveillance désigné par un fichier de configuration. - Protocoles distants :
GIT_ALLOW_PROTOCOLvauthttps:http:ssh. Un clone, un fetch ou un push qui utilise un chemin local, une URLfile://ou une URLgit://échoue avecfatal: transport 'file' not allowedoufatal: transport 'git' not allowed. - Commande SSH et demande d’identifiants : git dans votre hook ignore
core.sshCommandetcore.askPassprovenant des fichiers de configuration. Pour utiliser votre propre commande SSH, définissezGIT_SSH_COMMANDdans l’environnement du runner. Pour utiliser un programme de demande d’identifiants, définissez-yGIT_ASKPASS. Les sessions héritent de l’environnement du runner, donc les deux variables atteignent aussi le git de la session elle-même. N’y placez aucun identifiant. - Programmes gpg :
gpg.program,gpg.openpgp.program,gpg.x509.programetgpg.ssh.programsont des chemins que le runner définit, jamais des valeurs provenant d’un fichier de configuration. - Signature des commits : avec
--configure-git, les commits que vous créez depuis un hook sont signés au nom de la session. Sans ce flag,commit.gpgsignettag.gpgsignvalentfalse.
git -c à l’intérieur du hook :
- Paires de configuration : une paire
GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_nque vous exportez dans l’environnement du runner remplace la valeur du runner pour la même clé. Numérotez vos paires à partir de0et définissezGIT_CONFIG_COUNTsur leur nombre. Quand la dernière paire annoncée par le compteur est manquante, le runner ignore toutes vos paires et consigne une ligne[runner:warn]au démarrage. - Variables d’environnement git : le runner laisse
GIT_ALLOW_PROTOCOL,GIT_SSH_COMMANDetGIT_ASKPASStels que vous les avez définis dans son environnement. - Options
git -c: une optiongit -cà l’intérieur du hook remplace une paireGIT_CONFIG_KEY_n, celle du runner ou la vôtre. Elle ne modifie pasGIT_ALLOW_PROTOCOL,GIT_SSH_COMMANDniGIT_ASKPASS, que git lit avant toute configuration.
url.*.insteadOf et les filter drivers, depuis chaque fichier de configuration, y compris ceux que les sessions peuvent écrire. Un helper d’identifiants ou un filter driver désigné dans l’un de ces fichiers s’exécute comme un programme avec les privilèges de votre hook, et la configuration de ces fichiers peut encore modifier la destination d’un push depuis votre hook, y compris un push vers une URL que vous passez sur la ligne de commande.
Avant v2.1.280, le runner ne définissait aucun de ces paramètres, et sous --configure-git un commit créé depuis un hook échouait à moins que le hook ne passe -c commit.gpgsign=false.
command
S’exécute une fois par session après le checkout, à la place du spawn enfant intégré. Le hook reçoit le même environnement qu’un script wrapper et doitexec dans "$CLAUDE_RUNNER_CLAUDE_BIN" de la même manière. Utilisez le hook command pour garder toute la personnalisation dans un répertoire de hooks ; utilisez --exec-path quand le wrapper vit ailleurs. Si --exec-path est aussi défini, le flag prend la priorité et le hook command est ignoré.
Toujours exec le binaire du runner plutôt qu’un claude résolu par PATH ; sinon vous annulez l’épinglage de version.
Runners à la demande
Au lieu d’exécuter une flotte fixe, vous pouvez démarrer un runner par session. L’orchestrateur est une sous-commande séparée et sans état qui interroge Anthropic pour les demandes de spawn, une par session qui est en attente sans runner disponible, et exécute votre hookspawn-runner pour chacune. Votre hook soumet une charge de travail à votre plateforme : un Kubernetes Job, une instance EC2, un Nomad dispatch.
Les runners à la demande améliorent l’hygiène des identifiants. Sur une flotte fixe, le secret d’environnement vit sur chaque hôte runner, qui est le même hôte qui exécute les sessions utilisateur. Avec l’orchestrateur, le secret d’environnement reste uniquement sur l’hôte orchestrateur, qui n’exécute jamais le code utilisateur ; chaque runner généré reçoit un bon de travail à usage unique qui enregistre exactement un runner puis expire.
Pour démarrer l’orchestrateur, passez le secret d’environnement et un répertoire de hooks contenant un script spawn-runner exécutable :
--expected-spawn-seconds ; voir le contrat du hook.
Le hook spawn-runner
L’orchestrateur exécute${hooks-dir}/spawn-runner une fois par demande de spawn. Le hook doit soumettre le travail de manière asynchrone, sans attendre le démarrage du runner, et revenir dans --hook-timeout, 60 secondes par défaut. Le hook reçoit :
Le runner généré s’enregistre avec le bon de travail à la place du secret d’environnement :
- Démarrez-le avec le bon de travail : pointez
--environment-secret-filevers un fichier contenant le JWT du bon de travail, ou définissezSELF_HOSTED_RUNNER_ENVIRONMENT_SECRETà la valeur JWT. - Copiez le JWT avant la sortie du hook : l’orchestrateur supprime le fichier du bon de travail après la sortie du hook, donc copiez le JWT dans la charge de travail que vous soumettez, comme un Secret Kubernetes sur le Job généré, plutôt que de transmettre le chemin du fichier.
- Utilisez
--capacity 1sur les runners générés : un bon de travail lié à une session enregistre exactement un runner lié à cette session, donc une capacité plus élevée ajoute des emplacements qui ne reçoivent jamais de travail, et le runner enregistre un avertissement au démarrage. - Les bons de travail de pré-réchauffage s’enregistrent sans liaison : le runner de secours n’est pas lié à une session et revendique le travail en attente comme un runner de flotte fixe.
-
Soyez idempotent sur
CLAUDE_RUNNER_ORDER_ID. La redélivraison de la même demande doit générer au maximum un runner. Dérivez un nom de ressource déterministe à partir de l’ID de commande et laissez votre plateforme rejeter le doublon. Ne vous basez pas plutôt surCLAUDE_RUNNER_SESSION_ID. Chaque re-demande pour une session porte le même ID de session avec un nouvel ID de commande. Une charge de travail nommée ou dédupliquée par l’ID de session n’est pas recréée tant que la première existe. -
Ne réessayez pas la charge de travail. Un ID de commande signifie au maximum une charge de travail créée. Si le runner ne s’enregistre jamais, Anthropic re-demande avec un ID de commande frais après
--expected-spawn-seconds. -
Utilisez le contrat du code de sortie. Sortez avec le statut qui correspond au résultat :
- Sortie 0 : soumis.
- Sortie 1, ou hook toujours en cours d’exécution à l’expiration de
--hook-timeout: échec pouvant être réessayé. La session recule et est re-proposée. Après 10 demandes échouées consécutives, elle est bloquée comme après une sortie 2 ou supérieure. - Sortie 2 ou supérieure : échec ne pouvant pas être réessayé. La session est empêchée de générer un nouveau spawn jusqu’à ce qu’un utilisateur lui envoie un nouveau message ou qu’un Owner sélectionne Retry sur elle dans l’onglet Activity de l’environnement.
--expected-spawn-seconds. -
Définissez
--expected-spawn-secondsà au moins votre temps p99 entre la demande de spawn et l’enregistrement du runner. Mesurez à partir du moment où l’orchestrateur reçoit la demande de spawn, et incluez toute attente de capacité sur votre plateforme ainsi que le temps de démarrage. Cette valeur est le bail côté serveur, et le bon de travail expire avec lui, donc un runner dont la charge de travail prend plus de temps ne peut pas s’enregistrer. Toutes les répliques d’orchestrateur doivent utiliser la même valeur.
/healthz de l’orchestrateur pour les compteurs de file d’attente, puis ouvrez l’onglet Activity de votre environnement sur la page d’administration Cloud environments : développez une session échouée là pour son erreur de spawn, et sélectionnez Retry pour la re-demander.
Corriger les sessions rouvertes qui restent Queued ou affichent Failed
Si des sessions rouvertes restent Queued ou affichent Failed, ou si votre hook expire sur elles, l’une des causes possibles est un hook qui nomme ou déduplique les charges de travail par l’ID de session. Une session est rouverte lorsque son utilisateur lui envoie un message après la sortie de son runner. Par exemple, prenons un hook qui construit le nom de chaque charge de travail ainsi :- Lors de la première demande de spawn de la session, le hook crée une charge de travail portant ce nom, et un runner démarre
- Le runner se termine, par exemple après
--release-idle-session-min, et l’utilisateur rouvre plus tard la session - La demande de spawn suivante de la session porte le même ID de session et un nouvel ID de commande, donc le hook construit le même nom
- Si votre plateforme possède encore la charge de travail de la première demande, parce que rien ne l’a supprimée lorsque son runner s’est terminé, le hook ne crée aucune nouvelle charge de travail et aucun runner ne démarre
- Chaque demande de spawn ultérieure pour la session se termine de la même manière
Dans l’erreur de spawn de la dernière ligne,
60000 correspond à votre --hook-timeout en millisecondes. Lorsqu’un hook attend l’expiration de --hook-timeout, le journal de l’orchestrateur reçoit aussi une ligne timed out after.
Pour corriger le hook, construisez chaque nom ou clé à partir de CLAUDE_RUNNER_ORDER_ID, comme l’exige la règle 1 du contrat du hook. Dans l’exemple, le nom devient :
- Queued : la session reçoit d’elle-même sa prochaine demande de spawn
- Failed : sélectionnez Retry sur elle en tant qu’Owner, ou demandez à son utilisateur de lui envoyer un nouveau message
Faire quitter les runners à la demande lorsqu’ils sont inactifs
Par défaut, un runner que votre hook démarre ne se termine pas lorsque le tour de sa session s’achève, donc la charge de travail que vous avez soumise continue de s’exécuter. Pour que les runners se terminent, faites passer par votre hook ces flags du runner :
Supprimez la charge de travail lorsque le runner se termine, par exemple en définissant
ttlSecondsAfterFinished sur un Kubernetes Job ou en faisant supprimer la VM par son script de démarrage une fois terminé.
Envoyer la sortie d’un processus en arrière-plan vers un fichier
Si votre hookspawn-runner laisse un processus s’exécuter en arrière-plan, envoyez la sortie de ce processus vers un fichier que l’utilisateur du hook peut créer. Par exemple, ajoutez >> "$HOME/claude-runner.log" 2>&1 à la commande qui démarre le processus, avant tout &. Cette ligne de hook montre où se place la redirection, your-background-command représentant la commande que votre hook lance en arrière-plan :
- Environ une seconde après la sortie du hook, l’orchestrateur consigne
exited, but a process it started still has the hook's stdout or stderr open. À partir de cette ligne, il consigne jusqu’à 64 Ko supplémentaires de cette sortie sous[runner:hook:spawn-runner:after-exit jti=<order id>], puis abandonne le reste. - Une fois que l’orchestrateur s’est terminé, les écritures du processus à cet endroit échouent.
--hook-timeout tant qu’un processus qu’il avait démarré gardait ouverts le stdout ou le stderr du hook, ce qui s’affichait ainsi :
- Onglet Activity, dans l’erreur de spawn d’une session échouée :
spawn-runner hook timed out after 60000ms, ou les dernières lignes du stderr du hook. - Journal de l’orchestrateur :
timed out after 60000ms, sending SIGTERM to process tree.
Quitter le hook sans attendre le runner
Si chaque runner à la demande s’arrête environ 60 secondes après le démarrage de son hook, et que votre hook s’exécute à nouveau pour la même session, vérifiez si le hook attend le runner. Les 60 secondes correspondent au--hook-timeout par défaut ; si vous définissez votre propre valeur, les runners s’arrêtent au bout de cette durée après le démarrage du hook.
Un hook qui démarre claude self-hosted-runner sur l’hôte de l’orchestrateur et l’attend, au premier plan ou avec wait, est toujours en cours d’exécution à l’expiration de --hook-timeout. L’orchestrateur arrête alors le hook et les processus qui s’exécutent sous lui, y compris ce runner et le processus Claude de la session, de sorte qu’un tour encore en cours est interrompu.
- Pour confirmer que c’est le délai d’expiration qui arrête vos runners : recherchez dans le journal de l’orchestrateur
timed out after 60000ms, sending SIGTERM to process treeà chaque demande de spawn. Le60000correspond à votre--hook-timeouten millisecondes. - Pour corriger le problème : faites en sorte que le hook soumette le runner à votre plateforme puis sorte avec 0. Si vous augmentez plutôt
--hook-timeout, le runner s’arrête simplement plus tard.
Garder les échecs transitoires réessayables dans un hook shell
Dans un hook shell qui utiliseset -e, un échec qu’une nouvelle tentative aurait pu résoudre peut bloquer la session. Le hook s’arrête à la commande en échec et sort avec le statut propre à cette commande, et l’orchestrateur applique le contrat du code de sortie à ce statut. De nombreux échecs renvoient un statut de 2 ou plus, comme 127 quand une commande n’est pas installée et 22 de curl --fail sur une erreur HTTP, de sorte qu’ils bloquent la session dès son premier échec.
Une session que le hook a déjà bloquée reste bloquée jusqu’à ce qu’un utilisateur lui envoie un nouveau message ou qu’un Owner sélectionne Retry sur elle dans l’onglet Activity de l’environnement.
Pour transformer un tel échec en sortie 1, placez ces lignes directement sous la ligne #! du hook, au-dessus de tout ce qui peut échouer :
-
exit 2ou supérieur isolé : avec le trap défini, il devient une sortie 1. Pour une erreur qu’aucune nouvelle tentative ne peut corriger, appelez plutôtpermanentavec la raison, commepermanent "namespace claude-runners does not exist". Appelez-le dans le shell principal, pas à l’intérieur de$( ),( )ou d’un pipe. -
exec: ne commencez pas la dernière commande du hook parexec, carexecremplace le shell et le trap ne s’exécute pas. -
Second trap
EXIT: un secondtrap ... EXITremplace le premier, donc fusionnez les deux en un seul trap. Placez vos commandes de nettoyage directement aprèsrc=$?;et terminez chacune par|| true;. Le nettoyage s’exécute alors en cas d’échec comme en cas de succès, et une commande de nettoyage en échec ne définit pas le statut de sortie du hook. Ce trap fusionné illustre la forme,your-cleanup-commandreprésentant votre propre commande : -
Commandes autorisées à échouer : si le hook n’utilisait pas
set -eauparavant, il s’arrête désormais à la première commande qui renvoie une valeur non nulle, comme une recherche qui ne trouve rien. Si le hook agit sur le résultat, faites de cette commande la condition d’unif. S’il ignore le résultat, faites suivre la commande de|| true. -
La commande qui soumet la charge de travail : ne la faites pas suivre de
|| true. Avec|| true, le hook sort avec 0 même lorsque votre plateforme refuse la soumission, par exemple en cas de dépassement de quota, et la session attend un runner qui n’a jamais été démarré. Nommez la charge de travail d’aprèsCLAUDE_RUNNER_ORDER_ID, comme l’indique la première règle du hook. Une réponse indiquant que la ressource existe déjà correspond alors à une redélivraison de la même demande, donc traitez uniquement cette réponse comme un succès. Pour toute autre réponse, affichez-la sur stderr et sortez avec 1, ou appelezpermanentsi aucune nouvelle tentative ne peut corriger le problème. Cet exemple le fait pourkubectl create, avec unjob.yamlqui ne contient que la charge de travail :Le journal de l’orchestrateur affiche alors ce message. Si le hook crée d’autres objets, nommez chacun d’après l’ID de commande, donnez-lui sa propre commande et traitez de la même manière sa réponse indiquant qu’il existe déjà comme un succès. Pour un objet qui contient un secret, affichez un message fixe à la place de la sortie de la commande.
trap une ligne qui appelle une commande inexistante, comme no-such-command. Exécutez le fichier du hook depuis votre shell et vérifiez que echo $? affiche 1, puis supprimez la ligne.
Envoyer les requêtes de modèle vers Bedrock ou Agent Platform
Si votre organisation a besoin que les requêtes de modèle passent par son propre compte AWS ou Google Cloud, configurez le runner pour Amazon Bedrock ou Agent Platform de Google Cloud, anciennement Vertex AI. Chaque session démarrée par ce runner appelle alors le modèle dans votre compte cloud, avec vos identifiants cloud. Sans cette configuration, les sessions envoient les requêtes de modèle à l’API Anthropic. Le runner continue d’interroger Anthropic pour obtenir des sessions, et chaque session continue d’envoyer son flux d’événements àapi.anthropic.com. Le flux d’événements transporte les prompts, les réponses et les résultats des outils. L’exigence relative au forfait et l’exclusion de la Zero Data Retention décrites dans Disponibilité et limitations s’appliquent toujours.
Les sessions sont acheminées vers un environnement, et non vers un runner, et une session remise en file d’attente ou reprise peut s’exécuter sur un runner différent. Configurez tous les runners de l’environnement de la même manière. Avant de commencer, lisez ce qui diffère sur ces fournisseurs.
1
Préparer le compte cloud et vos règles de sortie
Configurez l’accès aux modèles, une stratégie ou un rôle à portée restreinte, ainsi que l’accès réseau :
- Amazon Bedrock : soumettez les détails du cas d’usage, puis créez la stratégie décrite dans Configuration IAM, en limitant
bedrock:InvokeModeletbedrock:InvokeModelWithResponseStreamaux profils d’inférence utilisés par vos sessions et aux modèles de fondation qui les sous-tendent - Agent Platform : activez l’API et demandez l’accès aux modèles, puis créez le rôle personnalisé décrit dans Configuration IAM, avec uniquement
aiplatform.endpoints.predict - Sortie : autorisez les endpoints de votre fournisseur dans vos règles de sortie. Consultez Exigences réseau. Si les sessions ne peuvent pas les atteindre, Claude Code peut continuer à réessayer pendant des heures avant que la session n’affiche une erreur.
2
Fournir aux sessions des identifiants à portée restreinte
Associez la stratégie ou le rôle de l’étape 1 à une identité qui ne peut rien faire d’autre. Pour les méthodes acceptées par Claude Code, consultez Configurer les identifiants AWS et Configurer les identifiants GCP.Vérifiez la méthode choisie au regard de ces comportements du runner :
- Endpoint de métadonnées : si vous refusez totalement aux sessions l’accès à l’endpoint de métadonnées cloud, les identifiants qu’il fournit, comme un profil d’instance, n’atteignent pas non plus Claude Code. Une identité web basée sur un fichier, comme IAM Roles for Service Accounts (IRSA) sur Amazon EKS ou un fichier d’identifiants Workload Identity Federation, n’en dépend pas.
- Renouvellement : une session peut survivre à ses identifiants, utilisez donc une méthode qui se renouvelle d’elle-même, comme une identité web basée sur un fichier
- Script wrapper : le runner lance votre script wrapper une seule fois par session, donc les identifiants qu’il exporte ne sont pas renouvelés. Claude Code lit les identifiants AWS depuis son environnement, donc si votre wrapper exporte déjà des identifiants AWS pour d’autres tâches, Claude Code peut signer les requêtes de modèle avec eux.
3
Définir les variables d'un seul fournisseur dans l'environnement du runner
Définissez les variables d’un seul fournisseur à l’endroit où vous définissez les autres variables d’environnement du runner, comme la spécification du conteneur ou l’unité de service, puis redémarrez le runner. Les exemples les présentent sous forme d’exports shell. Avec les runners à la demande, définissez-les sur la charge de travail que démarre votre hook
spawn-runner.Démarrez ces runners avec --confine-repo-settings enforce. Ce mode refuse les sessions sur les dépôts dont il signale les paramètres commités ; exécutez donc d’abord le mode par défaut warn et corrigez ce qu’il consigne dans les logs.- Amazon Bedrock
- Agent Platform de Google Cloud
Remplacez la région par la vôtre :Pour savoir comment Claude Code détermine la région, consultez Configurer Claude Code. Pour connaître le préfixe de profil d’inférence que Claude Code utilise pour votre région, consultez Préfixes des profils d’inférence inter-régions.
4
Vérifier que les variables ont atteint une session
Votre propre shell sur l’hôte est un processus différent ; effectuez donc la vérification depuis une session. Démarrez-en une dans l’environnement et demandez à Claude d’exécuter cette commande :Une ligne qui définit
CLAUDE_CODE_USE_BEDROCK ou CLAUDE_CODE_USE_VERTEX à 1 signifie que la variable a atteint la session. Si les deux apparaissent, Claude Code utilise Amazon Bedrock. L’absence de sortie signifie qu’aucune des deux ne l’a atteinte.La commande affiche la configuration, pas le trafic. Pour confirmer les requêtes elles-mêmes, recherchez-les dans les métriques ou les logs de requêtes de votre compte cloud. Si le premier message échoue, consultez plutôt le dépannage pour Amazon Bedrock ou Agent Platform.Différences par rapport aux sessions sur l’API Anthropic
Une session qui envoie des requêtes de modèle à Amazon Bedrock ou à Agent Platform de Google Cloud diffère d’une session sur l’API Anthropic sur les points suivants :- Stratégie depuis claude.ai : les paramètres gérés par le serveur n’atteignent pas ces sessions. Les stratégies d’organisation qu’un Owner définit dans les paramètres d’administration de Claude Code ne les atteignent pas non plus ; Claude Code ne les applique donc pas à l’intérieur de la session. Placez les règles sur lesquelles vous comptez dans le fichier de paramètres gérés de l’image du runner.
- Skills du compte : ces sessions ne téléchargent pas les skills activés pour le compte claude.ai d’une personne. Consultez Comment la configuration de chaque session est assemblée.
- Fichiers : les fichiers que les utilisateurs joignent à une session dans claude.ai ou dans l’application mobile ou de bureau ne l’atteignent pas, et Claude ne peut pas renvoyer de fichiers avec l’outil
SendUserFile. Placez plutôt les fichiers d’entrée dans le dépôt ou sur le runner. - Sélection du modèle : le plan de contrôle d’Anthropic envoie le modèle de chaque session, et lorsqu’une session démarre sans modèle, Claude Code utilise son modèle par défaut pour le fournisseur. Vous ne pouvez pas choisir le modèle avec
ANTHROPIC_MODELouANTHROPIC_DEFAULT_MODELdans l’environnement du runner, mais vous pouvez épingler ce vers quoi un alias est résolu :ANTHROPIC_MODELetANTHROPIC_DEFAULT_MODEL: le runner les supprime de l’environnement qu’il transmet aux sessions, même si les exemples des pages des fournisseurs définissentANTHROPIC_MODEL.- Variables d’épinglage par famille : les variables décrites dans Épingler les versions de modèle pour Amazon Bedrock et Agent Platform atteignent bien les sessions. Elles déterminent ce vers quoi un alias comme
opusest résolu, et non ce vers quoi un ID de modèle complet est résolu.
- Modèles que votre compte ne fournit pas : une session peut échouer sur un message avec une erreur qui nomme le modèle. Activez les modèles que vos développeurs peuvent choisir, le modèle d’arrière-plan décrit dans Épingler les versions de modèle, ainsi que le modèle de classifieur utilisé par le mode auto. Sur Amazon Bedrock, autorisez chacun d’eux dans votre stratégie.
- Recherche web et mode rapide : la recherche web n’est pas disponible sur Amazon Bedrock, et le mode rapide n’est disponible sur aucun des deux fournisseurs. Pour les autres fonctionnalités qui varient selon le fournisseur, consultez Fonctionnalités de la CLI qui varient selon le fournisseur.
Serveurs MCP
Pour rendre des serveurs MCP disponibles dans chaque session, ajoutez-les lors du build de l’image avec la même commandeclaude mcp add que celle utilisée sur une installation de bureau. Si votre runner est un simple processus plutôt qu’un conteneur, exécutez la même commande sur l’hôte en tant qu’utilisateur du runner, puis redémarrez le runner : celui-ci ne lit la configuration de l’hôte qu’une seule fois au démarrage. Le flag --scope user est obligatoire ; la portée locale par défaut écrit sous une clé propre à chaque répertoire que le runner ne transmet pas aux sessions. Par exemple, dans votre Dockerfile :
mcpServers du fichier .claude.json de l’hôte, qui se trouve à côté de ~/.claude/ et non à l’intérieur, et le runner ne transmet que cette clé à la configuration isolée de chaque session ; l’état du compte et l’historique des projets sont écartés. Pour vérifier que les serveurs ont bien atteint les sessions, démarrez une session sur l’environnement et demandez à Claude de lister ses outils MCP ; le runner consigne également un avertissement au démarrage pour toute entrée capturée dont il ne reconnaît pas le type, puis supprime cette entrée, ce qui vous permet de comprendre pourquoi ce serveur est absent des sessions. Lorsque SELF_HOSTED_RUNNER_HOST_CONFIG_DIR est défini, le runner lit .claude.json dans ce répertoire à la place ; faire pointer la variable vers un répertoire vide désactive donc aussi la transmission des serveurs MCP.
Claude Code charge également des serveurs MCP à partir d’autres sources :
- Le fichier MCP géré de portée entreprise, à son chemin système standard :
/etc/claude-code/managed-mcp.jsonsur les hôtes de runner Linux,/Library/Application Support/ClaudeCode/managed-mcp.jsonsur les hôtes macOS. Utilisez-le pour les parcs verrouillés où seuls les serveurs listés par un administrateur peuvent être chargés. Consultez le contrôle exclusif avec managed-mcp.json pour les règles de priorité. Lorsque ce fichier est présent sur l’hôte du runner, Claude Code ignore les serveurs MCP que le plan de contrôle d’Anthropic fournit à une session, y compris les connecteurs claude.ai, et les nomme dans un avertissement sur la sortie stderr du processus enfant de la session, que le runner enregistre au niveau de logdebug. Avant la v2.1.229, ces sessions se terminaient au démarrage avecYou cannot dynamically configure MCP servers when an enterprise MCP config is present. - La clé
managedMcpServersdans les paramètres gérés sur l’hôte du runner : elle fournit des serveurs HTTP et SSE sans prendre le contrôle exclusif, de sorte que les serveurs des autres sources sont toujours chargés. Nécessite Claude Code v2.1.259 ou une version ultérieure. <repo>/.mcp.json: portée projet. En dehors d’un fil de projet, faites un commit du fichier dans le dépôt ; ses serveurs sont approuvés automatiquement dans les sessions cloud. Dans une session comportant plusieurs dépôts, le fichier d’un seul dépôt au plus est chargé.- Un fichier
--mcp-configpassé par votre script wrapper : ajoutez le flag après"$@", comme dansexec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.json. Pour que les serveurs de ce fichier continuent de fonctionner lorsque les serveurs fournis à la session sont mis à jour en cours de session, faites en sorte que votre wrapper transmette l’environnement complet au processus enfant, comme le faitexec. Avant la v2.1.299, les serveurs stdio, HTTP et SSE de ce fichier s’arrêtaient à la première mise à jour de ce type qui ne les nommait pas.
api.anthropic.com. Les sessions créées par programmation, comme les dispatches via la CLI, ne bénéficient pas de la fourniture des connecteurs ; fournissez-leur plutôt des serveurs MCP via l’une des autres sources listées dans cette section. Le jeton OAuth du processus enfant ne comporte pas de scope permettant de récupérer directement les connecteurs ; le processus enfant ne tente donc pas lui-même cette récupération : la fourniture est pilotée par le serveur.
Dans une session qui a reçu un connecteur, un serveur portant le même nom et provenant du fichier
.claude.json de l’hôte ou du fichier .mcp.json d’un dépôt n’est pas connecté. Pour utiliser les deux, donnez un nom différent à votre serveur.settings.json ne contient pas de définitions de serveurs MCP, et il n’existe pas de champ mcpServers de premier niveau dans le schéma des paramètres. Dans les paramètres gérés, fournissez plutôt les serveurs avec la clé managedMcpServers.
Les sessions héritent de l’environnement du runner ; définissez donc ENABLE_TOOL_SEARCH à ce niveau pour contrôler la recherche d’outils MCP pour chaque session lancée par un runner ; la page MCP décrit les valeurs possibles.
Attendre les serveurs MCP avant le premier tour
Une session auto-hébergée attend brièvement les serveurs MCP qui sont encore en cours de connexion, à deux moments distincts. Un serveur qui manque une attente voit ses outils absents au début du premier tour ; ils deviennent disponibles plus tard sans aucune action de votre part. Les deux attentes sont les suivantes :- Démarrage de la session : avant que la liste des outils ne soit établie pour la première fois, la session attend par défaut jusqu’à 5 secondes un serveur HTTP ou SSE dont l’entrée définit
alwaysLoad: true, ou tous les serveurs lorsque vous définissezMCP_CONNECTION_NONBLOCKING=0dans l’environnement du runner. Sinon, les serveurs HTTP et SSE se connectent en arrière-plan. Pendant cette attente, la session s’initialise plus lentement.MCP_CONNECT_TIMEOUT_MSmodifie la valeur par défaut de 5 secondes. - Premier tour : après l’arrivée du message, le premier tour attend jusqu’à 2 secondes les serveurs stdio qui sont encore en cours de connexion. Pendant cette attente, la première réponse est plus lente. Pour modifier la durée de cette attente, définissez
CLAUDE_CODE_MCP_STARTUP_WAIT_MSdans l’environnement du runner. Cela ne modifie pas les serveurs couverts par l’attente. Nécessite Claude Code v2.1.274 ou une version ultérieure.
claude mcp add n’a pas de flag alwaysLoad. Pour définir cette clé, ajoutez plutôt le serveur avec claude mcp add-json, qui la reçoit dans le JSON du serveur et l’écrit dans .claude.json. Dans votre Dockerfile :
Désactiver les outils de session intégrés
Le plan de contrôle d’Anthropic attache son propre serveur MCP, nommé Claude Code Remote, aux sessions cloud. Claude utilise les outils de ce serveur pour créer des tâches planifiées cloud, démarrer et piloter d’autres sessions cloud, attacher des dépôts supplémentaires et suivre l’activité des pull requests. Pour désactiver l’ensemble du serveur, ajoutez une règle de refus au niveau du serveur à vos paramètres. Le plan de contrôle enregistre le serveur sous l’un de trois noms, selon la manière dont la session a été créée. Claude Code fait correspondre le nom d’une règle de manière exacte, casse comprise ; écrivez donc la règle une fois par nom, comme indiqué :mcp__Claude_Code_Remote__add_repo. Pour empêcher complètement le serveur de se connecter plutôt que de supprimer ses outils, ajoutez plutôt les trois noms sans le préfixe mcp__ en tant qu’entrées serverName sous deniedMcpServers.
Placez les règles dans les paramètres gérés par le serveur pour atteindre les sessions sans modifier le runner, ou dans ~/.claude/settings.json sur le runner. Sur un runner qui envoie les requêtes de modèle à Bedrock ou Agent Platform, utilisez ce fichier, car les paramètres gérés par le serveur n’atteignent pas ces sessions. La section Permissions et approbation des outils explique comment les paramètres du runner atteignent les sessions.
Pour vérifier que les règles ont pris effet, démarrez une session sur l’environnement et demandez à Claude de lister ses outils MCP. Claude Code retire un outil refusé du contexte de Claude ; les outils refusés sont donc absents de sa réponse.
Inviter les sessions à envoyer leur travail
Les sessions hébergées par Anthropic exécutent un hookStop, le hook Claude Code qui s’exécute quand Claude finit de répondre, qui invite Claude à valider et envoyer son travail. Le runner n’en installe pas. Sans lui, une session qui se termine avec des changements non commis laisse ce travail uniquement sur le disque du runner, et le bouton Create PR dans claude.ai/code reste inactif jusqu’à ce que la branche existe sur le distant.
L’implémentation de référence ci-dessous a deux parties. Fusionnez le bloc de paramètres dans ~/.claude/settings.json sur l’hôte runner, que le runner sème dans chaque session, et enregistrez le script comme ~/.claude/hooks/stop-hook-nudge.sh sur l’hôte runner et rendez-le exécutable :
$CLAUDE_PROJECT_DIR.
Le hook Stop ne fait que solliciter Claude. Pour que le commit et l’envoi fonctionnent, configurez :
- Une identité git : définissez
user.nameetuser.emaildans votre image de runner, ou démarrez le runner avec--configure-git - Des identifiants d’envoi : utilisez git géré par Anthropic, ou fournissez des identifiants comme le décrit Intégrer la configuration git dans votre image
- Pour les sessions reprises : définissez
--push-outcome-on-releasesur chaque runner de l’environnement. Une session reprise démarre alors à partir des commits que Claude a envoyés sur la branche de la session. Pour les limites de ce flag, consultez Les sessions reprises perdent le travail non envoyé.
timeout dans les paramètres du hook Stop et redémarrez les runners. Le git status du script ignore core.fsmonitor, il peut donc prendre plus longtemps que les 10 secondes définies à cet endroit. Un hook qui expire termine le tour sans solliciter Claude.
Sous-modules et hook Stop
Le hook Stop vérifie sur quel commit se trouve chaque sous-module, et non les fichiers qu’il contient :- Un nouveau commit dans un sous-module : Claude est sollicité jusqu’à ce que le dépôt parent l’enregistre, même si
.gitmodulesdéfinitignore = allpour ce sous-module - Des fichiers non commités dans un sous-module : Claude n’est pas sollicité, ce travail reste donc uniquement sur le disque du runner
- Des commits non envoyés dans un sous-module : le hook Stop ne vérifie pas s’ils ont été envoyés, le parent peut donc pointer vers un commit que personne d’autre ne peut récupérer
Remplacer une version antérieure du script du hook Stop
RecherchezGIT_OPTIONAL_LOCKS dans votre copie de stop-hook-nudge.sh. S’il est absent, vous disposez d’une version antérieure. Remplacez-la par le script actuel partout où vous l’avez installée, puis redémarrez les runners. Conservez vos paramètres du hook Stop, sauf pour augmenter timeout sur un checkout volumineux.
- Quand : si vos sessions s’exécutent avec le sandbox Bash activé, remplacez-la dès maintenant. Sinon, remplacez-la lors de votre prochain build d’image ou redémarrage du runner.
- Ce qui change : les fichiers non commités dans un sous-module ne sollicitent plus Claude. Un nouveau commit dans un sous-module que
.gitmodulesmarqueignore = allle sollicite désormais.
Permissions et approbation d’outils
Une session auto-hébergée n’a pas de terminal attaché, donc une invite de permission sans réponse bloque le tour jusqu’à ce que l’utilisateur réponde dans l’interface utilisateur. Le plan de contrôle d’Anthropic envoie la liste des outils de chaque session et les règles de permission avec la charge utile de travail ; la configuration par défaut pré-approuve les appels d’outils de routine, y comprisBash, et les sessions cloud pré-approuvent les éditions de fichiers quel que soit le mode. Un appel que rien ne pré-approuve invite via l’interface utilisateur de la session.
Épinglez uniquement le mode auto sur un environnement dont les conteneurs de session s’exécutent avec sortie réseau par défaut-refuser et le reste de la section de durcissement en place. Les appels d’outils de routine, y compris les demandes réseau
Bash, s’exécutent sans un humain dans la boucle sur l’ensemble d’outils pré-approuvés par défaut et en mode auto, donc la limite réseau est ce qui limite où ces appels peuvent atteindre.command. Le mode auto permet aux sessions de s’exécuter sans invites de permission de routine : un modèle de classificateur séparé examine les actions avant qu’elles ne s’exécutent et bloque celles qu’il rejette, et les règles d’ask explicites forcent toujours une invite ; la page des modes de permission couvre ce que le classificateur vérifie. Le runner ajoute les flags calculés par le serveur avant d’invoquer le wrapper, et pour les flags à valeur unique comme --permission-mode l’analyseur honore la dernière occurrence, donc un flag que vous ajoutez après "$@" remplace la valeur envoyée par le serveur :
--allowed-tools avec vos règles, par exemple --allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*". Les flags de liste comme --allowed-tools et --disallowed-tools s’accumulent entre les occurrences plutôt que de se remplacer, donc vos règles s’appliquent en plus de toutes les règles que le plan de contrôle envoie. Pour réduire, ajoutez --disallowed-tools, qui refuse les outils même si une autre règle les permet.
Comment la configuration de chaque session est assemblée
Le runner donne à chaque session son propre répertoire de configuration, semé à partir d’un instantané du~/.claude/ de l’hôte que le runner capture une fois au démarrage : settings.json, CLAUDE.md, hooks, agents, commandes et skills dans votre image runner s’appliquent à chaque session comme la ligne de base au niveau utilisateur. Si vous modifiez la configuration sur un hôte en cours d’exécution, la modification ne prend effet qu’après le redémarrage du runner.
Définissez SELF_HOSTED_RUNNER_HOST_CONFIG_DIR pour semer à partir d’un chemin différent, ou pointez-le vers un répertoire vide pour désactiver le semis.
Les sessions lisent également ces fichiers de paramètres :
- Paramètres du projet : en dehors d’un fil de projet, un
.claude/settings.jsoncommité dans le dépôt se superpose à la ligne de base au niveau utilisateur. Dans une session avec plusieurs dépôts, le fichier d’au plus un dépôt prend effet. - Paramètres gérés : les sessions lisent
managed-settings.jsonà partir du chemin système standard dans votre image runner. Pour savoir si ses clés s’appliquent aux côtés des paramètres gérés par le serveur, voir comment Claude Code combine les sources gérées.
python3, installez donc python3 dans votre image runner.
- Où ils atterrissent : le runner écrit chaque script de hook fourni dans un sous-répertoire réservé
hooks/.ccr-launcher/du répertoire de configuration de la session et enregistre les scripts dans un fichier de paramètres séparé qu’il transmet à la session avec--settings, laissant lesettings.jsonsemé et vos propres scripts àhooks/<name>intacts. Le runner recrée le sous-répertoire réservé pour chaque session et ne sème pas le contenu de l’hôte à~/.claude/hooks/.ccr-launcher/dans les sessions. - Qui les crée : le plan de contrôle remplit les scripts à partir de constantes fixes dans son propre déploiement, jamais à partir d’entrées par session ou tierces.
- Ce qui les gouverne toujours : les hooks livrés via
--settingsentrent dans la configuration de hook fusionnée ordinaire, pas le niveau géré, donc vos paramètres gérés s’appliquent toujours.disableAllHooksles désactive, et ils ne font pas partie des catégories queallowManagedHooksOnlygarde chargées.
.claude/skills/ du dépôt.
En dehors des sessions Claude Tag, une session dans un environnement auto-hébergé s’exécute avec la mémoire automatique désactivée par défaut. Pour les instructions qui doivent persister d’une session à l’autre, utilisez le CLAUDE.md de votre image runner ou du dépôt.
L’instantané du ~/.claude/ de l’hôte pris par le runner exclut le répertoire projects/. L’emplacement de stockage par défaut de la mémoire automatique se trouve sous ce répertoire. Si vous y placez des fichiers de mémoire, le runner ne les sème pas dans les sessions, et ils n’activent pas la mémoire automatique.
Arrêter les sessions qui ne parviennent pas à récupérer les paramètres gérés par le serveur
Pour empêcher une session qui récupère des paramètres gérés par le serveur de démarrer lorsque cette récupération échoue, définissezforceRemoteSettingsRefresh sur true dans le managed-settings.json de votre image runner. Une réponse de l’API d’Anthropic indiquant que votre organisation n’a aucun paramètre géré par le serveur compte comme une récupération réussie.
Avant d’ajouter la clé, vérifiez si l’environnement exécute des sessions de canal Claude Tag ou envoie des requêtes de modèle à Bedrock ou Agent Platform :
- Sessions de canal Claude Tag : n’ajoutez pas la clé sur les runners d’un environnement vers lequel un Owner achemine des canaux Claude Tag. L’API d’Anthropic refuse la requête de paramètres gérés par le serveur de ces sessions, donc avec la clé définie, elles ne démarrent pas. Placez leurs règles dans le
managed-settings.jsonde l’image runner, qu’elles lisent toujours. Pour utiliser la clé pour vos autres sessions, exécutez-les dans un environnement séparé. - Bedrock ou Agent Platform : les sessions sur ces fournisseurs ne récupèrent pas les paramètres gérés par le serveur, elles démarrent donc que vous définissiez la clé ou non. Appliquez plutôt vos règles à partir du même
managed-settings.json.
managed-settings.json définit la clé :
managed-settings.json
Your organization requires remote managed settings to load, but they could not be loaded.
Paramètres de dépôt dans les sessions avec plusieurs dépôts
Dans un fil d’un projet, aucun.claude/settings.json de dépôt ne prend effet comme paramètres du projet et aucun .mcp.json ni aucun skill de dépôt ne se charge, quel que soit l’endroit où le fil démarre. Pour fournir un skill à ces fils, ajoutez-le à votre image runner.
Dans toute autre session avec plusieurs dépôts, Claude Code lit les paramètres du projet à partir du répertoire dans lequel la session démarre, donc le .claude/settings.json d’au plus un dépôt prend effet comme paramètres du projet. Un hook défini dans le fichier d’un autre dépôt ne s’exécute pas, une règle de refus qui s’y trouve ne s’applique pas, et son env n’est pas défini.
--capacity 1, la valeur par défaut, avec le checkout intégré : la session démarre dans le premier dépôt de sa liste de dépôts. Le.claude/settings.jsonde ce dépôt prend effet comme paramètres du projet et son.mcp.jsonse charge, contrairement à ceux des autres dépôts.- Un
--capacitysupérieur à un, ou un hookcheckout: la session démarre dans un répertoire propre à la session qui contient les checkouts. Aucun.claude/settings.jsonde dépôt ne prend effet comme paramètres du projet, aucun.mcp.jsonde dépôt ne se charge, et$CLAUDE_PROJECT_DIRdans une commande de hook correspond à ce répertoire, et non à un checkout.
CLAUDE.md de chaque dépôt se charge quel que soit l’endroit où la session démarre. En dehors d’un fil de projet, ses skills se chargent aussi. Le runner transmet chaque dépôt à Claude Code comme répertoire supplémentaire, donc Claude Code lit aussi les clés enabledPlugins et extraKnownMarketplaces du .claude/settings.json de chaque dépôt.
Pour exécuter un hook ou appliquer une règle de permission dans chaque session, placez-le dans ~/.claude/settings.json sur l’hôte du runner. Le runner sème le fichier de l’hôte dans chaque session, quel que soit l’endroit où la session démarre. Écrivez un chemin dans une règle Read ou Edit sous forme de pattern absolu // ou relatif au répertoire personnel ~/, car les autres patterns sont ancrés à la source des paramètres ou au répertoire courant.
Règles de permission validées dans le référentiel
Ne mettez pas une entrée"Edit", "Write" ou "NotebookEdit" nue dans un permissions.allow validé dans le référentiel. Une règle d’outil de fichier nue correspond à l’outil quel que soit le chemin, accordant des écritures n’importe où sur l’hôte plutôt que uniquement l’espace de travail, donc la garde de confinement de portée d’écriture du runner signale la session ; avec --confine-repo-settings enforce elle refuse de générer la session au lieu de consigner et continuer. Voir la section de durcissement.
Un référentiel n’a besoin d’aucune règle d’outil de fichier du tout : les sessions cloud pré-approuvent les éditions de fichiers quel que soit le mode. Si vous validez une règle, limitez-la à l’espace de travail, comme "Edit(/**)"; une seule barre oblique de début est relative à la racine du projet, qui est l’espace de travail de la session. Les règles d’outil de fichier nues sont correctes dans le settings.json au niveau de l’hôte de l’opérateur, puisque ce fichier n’est pas validé dans le référentiel.
Un defaultMode de auto n’est honoré que à partir du fichier de paramètres au niveau de l’image ou au niveau utilisateur, donc un référentiel extrait ne peut pas se donner le mode auto. Pour les modes que les sessions cloud acceptent et la syntaxe complète des règles, voir modes de permission.
Prochaines étapes
- Référence : chaque flag CLI, variable d’environnement et métrique
- Vérifier l’identité de la session : valider le token de session à partir de services en dehors du runner