Distribuisci ambienti self-hosted in produzione
Esegui runner self-hosted in produzione: hardening della sicurezza, controllo dell'egress di rete, credenziali git, ricette Kubernetes e Compose, e risoluzione dei problemi.
[!NOTE]
Gli ambienti self-hosted sono in beta pubblica sui piani Team ed Enterprise; Disponibilità e limitazioni copre il percorso di abilitazione. Questa pagina copre l'esecuzione della flotta in produzione; consulta la guida rapida per il tuo primo runner e sessione.
Un ambiente self-hosted esegue sessioni cloud di Claude Code su runner che distribuisci all'interno della tua rete, e in produzione quelle sessioni eseguono codice diretto dal modello per conto di chiunque possa inviare una sessione all'ambiente. Questa pagina è per l'operatore che porta un ambiente funzionante in produzione. Funziona attraverso la distribuzione in ordine: cosa bloccare prima di connettere sistemi reali, l'egress di cui ha bisogno la flotta, come le sessioni si autenticano al tuo host git, le ricette di distribuzione stesse, e cosa controllare quando le sessioni si comportano male.
Hardening della tua distribuzione
Un runner self-hosted esegue codice arbitrario diretto dal modello sulla tua infrastruttura per conto di chiunque possa inviare una sessione al suo ambiente. Questo è qualsiasi membro della tua organizzazione Anthropic, e chiunque possa avviare una sessione del canale Claude Tag in un ambito che un Owner ha instradato all'ambiente. Lavora su ogni elemento prima di connettere un ambiente ai sistemi di produzione:
-
Container effimeri per sessione: esegui ogni processo runner in un container o VM fresco che viene distrutto quando il processo esce, con
--capacity 1e il valore predefinito--drain-grace-sec 0in modo che ogni container serva esattamente una sessione. A una capacità più alta, o con un drain grace positivo, un container serve più sessioni dallo stesso owner bloccato; vedi Ciclo di vita del runner. Non riutilizzare un filesystem tra i riavvii del runner, tranne nella configurazione deliberata pre-warmed checkout, e mai tra owner.- Quando il runner arresta una sessione, non invia alcun segnale a un processo ancora in esecuzione dopo che il suo comando shell è terminato, come un servizio che si è demonizzato. Distruggere il container o la VM termina quel processo.
-
Nessuna credenziale ampia nell'immagine: non includere chiavi SSH di lunga durata, credenziali del provider cloud, o token di accesso personale che concedono più di quanto una sessione necessita. Crea credenziali utilizzate durante una sessione, come token push o API, per sessione dal tuo script wrapper. Il clone iniziale avviene prima che lo script wrapper venga eseguito, quindi gestiscilo con un
checkoutlifecycle hook, oppure con--use-anthropic-git-proxyquando tutti i repository di una sessione si trovano su github.com. Per entrambi, vedi Configura git. -
Mantieni le credenziali GitHub dell'host lontane dalle sessioni: Claude può usare qualsiasi credenziale GitHub che una sessione può leggere, con qualunque accesso quella credenziale conceda. Tieni le credenziali GitHub con ambito ampio dell'host runner fuori da qualsiasi cosa una sessione possa leggere. Una tale credenziale può essere un token di accesso personale, il token che
gh auth loginsalva per il tuo account, o unGH_TOKENnell'ambiente del runner.- Con git gestito da Anthropic: con una tale credenziale, Claude raggiunge GitHub direttamente invece che tramite git gestito da Anthropic.
- Senza git gestito da Anthropic: una credenziale di clone può restare nell'immagine se ne limiti l'ambito in modo rigoroso come descritto in Includi la configurazione git nella tua immagine.
-
Mantieni il segreto dell'ambiente lontano dagli host che eseguono sessioni: il segreto dell'ambiente può registrare runner e raccogliere qualsiasi sessione in coda sull'ambiente. Su una flotta fissa vive su ogni host runner, dove il codice di qualsiasi sessione può leggere il file segreto. Preferisci runner on-demand, dove il segreto rimane sull'host dell'orchestrator, che non esegue mai codice utente, e ogni runner riceve un ordine di lavoro monouso che registra esattamente un runner. Su una flotta fissa, tratta il file environment-secret come leggibile da ogni sessione e ruota il segreto dopo qualsiasi sospetto compromesso della sessione.
-
Egress di rete default-deny: limita il traffico in uscita del container runner e sessione al tuo confine di rete su ogni ambiente; Default-deny egress copre cosa consentire e perché.
-
IAM host con privilegi minimi: l'identità di calcolo allegata all'host runner, come un profilo di istanza o un account di servizio del nodo, dovrebbe concedere solo ciò di cui il runner stesso ha bisogno. Le sessioni dovrebbero ottenere le proprie credenziali attraverso il tuo script wrapper piuttosto che ereditare quelle dell'host.
-
Blocca l'endpoint dei metadati cloud dalle sessioni: mantenere le sessioni fuori dall'identità dell'host richiede il blocco del loro accesso all'endpoint dei metadati cloud, e le politiche di egress a livello di subnet non intercettano il traffico dei metadati link-local, quindi bloccalo nel container stesso:
- IMDSv2 con un hop limit di uno
- GKE Workload Identity con metadata concealment
- Un esplicito deny per
169.254.169.254nello spazio dei nomi di rete del container della sessione
Il blocco si applica anche al tuo script wrapper e ai lifecycle hook, poiché condividono il container. Autentica qualsiasi scambio di token con il session JWT contro il tuo servizio di token su egress allowlisted, o usa un'identità web basata su file come IAM Roles for Service Accounts (IRSA) su Amazon EKS.
-
Isolamento del filesystem per runner: ogni processo runner ottiene la propria directory di lavoro che nessun altro processo sull'host può leggere o scrivere. Rendi
--hooks-dir, lo script wrapper, e la~/.claude/dell'host di sola lettura per la sessione, sia incorporato nell'immagine che montato in sola lettura. -
Dispatch non ha controllo di accesso per ambiente: qualsiasi membro della tua organizzazione Anthropic può inviare una sessione a qualsiasi suo ambiente. Se un Owner instrada i canali Claude Tag all'ambiente, chiunque l'impostazione di accesso Claude Tag ammette può avviare sessioni di canale che vengono eseguite lì. Per impostazione predefinita, questo è chiunque nel workspace Slack connesso, con o senza un account Claude. Tratta ogni host runner come raggiungibile per l'esecuzione di codice da parte di chiunque possa inviare a esso, e posiziona sull'host runner solo i dati e le credenziali che tutte quelle persone sono autorizzate a leggere.
--lock-to-accountlimita quale account le sessioni di un dato host eseguono, ma non restringe chi può inviare all'ambiente. Per rendere gli ambienti self-hosted l'unica opzione di selezione, un Owner può nascondere gli ambienti ospitati da Anthropic per l'intera organizzazione dalla pagina Cloud environments. -
Applica la guardia repo-settings: scegli la modalità di guardia con
--confine-repo-settings. Il valore predefinitowarnregistra una violazione e comunque genera la sessione,enforcerifiuta la sessione, eoffdisabilita la scansione. Il runner scansiona le impostazioni impegnate di ogni repository per:- Una concessione che si risolve al di fuori del workspace della sessione stessa: una voce
additionalDirectories, una regolaEdit,Write, oNotebookEditinpermissions.allow, o una vocesandbox.filesystem.allowWriteoallowRead - Un blocco
envnon vuoto - Un override della postura dell'operatore come
sandbox.enabled: false
La guardia viene eseguita indipendentemente da
--trust-workspace, e non copre gli hook del repository,.mcp.json, o le regole Bash; vedi Permessi e approvazione degli strumenti per dove quelle concessioni appartengono. - Una concessione che si risolve al di fuori del workspace della sessione stessa: una voce
[!NOTE]
Se la tua organizzazione ha abilitato l'allowlist IP, aggiungi gli indirizzi di egress pubblici dei tuoi runner e dei container di sessione all'allowlist prima di avviarli. Se esegui runner on-demand, aggiungi anche l'indirizzo dell'host dell'orchestrator. Non fare affidamento sull'allowlist come controllo di rete per il traffico del runner o della sessione. Applica invece default-deny egress al tuo confine di rete.
Requisiti di rete
Il runner e i figli della sessione che genera fanno connessioni in uscita agli host di seguito. Limita l'egress del container della sessione a questi host e ai servizi interni specifici che le sessioni devono raggiungere; Default-deny egress copre come e perché.
Questi host sono sempre richiesti:
| Host | Porta | Utilizzato per |
|---|---|---|
api.anthropic.com |
443, HTTPS; WSS per il git gestito da Anthropic | Piano di controllo del runner e streaming della sessione, inferenza del modello, flag delle funzionalità, analitiche dei prodotti, recuperi della chiave JWKS, firma dei commit e git gestito da Anthropic quando --use-anthropic-git-proxy è impostato |
Il tuo host git, come github.com o il tuo host GitHub Enterprise |
443 o 22 | Clonazione e push dei repository su ogni host git usato dalle sessioni del runner. Su un runner che usa --use-anthropic-git-proxy, vedi quando il percorso github.com è ancora necessario. |
Un runner che usa --use-anthropic-git-proxy instrada il suo traffico git verso github.com attraverso api.anthropic.com, quindi non ha bisogno del percorso dell'host git per github.com. Ne ha comunque bisogno se imposti --push-outcome-on-release o esegui il push da un hook post-session.
Se questi host sono necessari dipende dalla tua configurazione:
| Host | Porta | Quando richiesto |
|---|---|---|
api.github.com |
443 | Con o senza --use-anthropic-git-proxy, quando l'immagine del runner include la GitHub CLI e fornisci alle sessioni una credenziale github.com, come un GH_TOKEN |
downloads.claude.ai |
443 | Al momento dell'installazione, quando installi o aggiorni Claude Code sull'host con il programma di installazione nativo; lo script install.sh stesso viene servito da claude.ai. Al momento dell'esecuzione della sessione, solo quando le sessioni installano plugin dal marketplace ufficiale di Anthropic. |
storage.googleapis.com |
443 | Al momento dell'esecuzione della sessione, per i conteggi di installazione dei plugin e i metadati mostrati in /plugin. |
code.claude.com e claude.com |
443 | Ricerche di documentazione dall'agente claude-code-guide integrato e richieste WebFetch pre-approvate durante le sessioni. Il blocco di questi host influisce solo sulle ricerche di documentazione. |
*.frame.claudeusercontent.com |
443 | Solo quando lo strumento Artifact è disponibile per le sessioni nella tua organizzazione; i valori predefiniti variano in base al piano, secondo la tabella di disponibilità lì. Imposta CLAUDE_CODE_DISABLE_ARTIFACT=1 sul runner per mantenere lo strumento disabilitato indipendentemente dall'impostazione dell'organizzazione. |
registry.npmjs.org |
443 | Quando una sessione installa un plugin, sia per il recupero dei pacchetti plugin da fonte npm che per l'installazione delle dipendenze Node.js di un plugin, o quando un server MCP lanciato da npx viene eseguito |
| Un mirror interno dei registri di pacchetti usati dalle tue build, o i registri stessi | 443, o la porta del tuo mirror | Quando le sessioni installano dipendenze per compilare o testare i tuoi repository |
http-intake.logs.us5.datadoghq.com |
443 | Metriche operative di Anthropic. Solo quando CLAUDE_CODE_BYOC_ENABLE_DATADOG=1 è impostato; disabilitato per impostazione predefinita negli ambienti self-hosted. |
browser-intake-us5-datadoghq.com |
443 | Caricamenti di rapporti di errore di Anthropic, inviati solo quando la segnalazione di errori è abilitata per l'account della sessione. Soppressa da DISABLE_ERROR_REPORTING=1 o DISABLE_TELEMETRY=1. |
Gli endpoint del tuo provider cloud per le richieste al modello, le ricerche dei modelli e il rinnovo delle credenziali, come bedrock-runtime.us-east-1.amazonaws.com o aiplatform.googleapis.com |
443 | Solo quando il runner invia richieste al modello ad Amazon Bedrock o ad Agent Platform di Google Cloud |
Non è necessario aggiungere questi host all'allowlist per il traffico del runner o della sessione:
statsig.anthropic.com,*.sentry.io,claude.aieplatform.claude.com: questi host appaiono in alcuni elenchi di controllo di rete enterprise più vecchi, ma il runner non li raggiunge. I recuperi dei flag delle funzionalità vanno aapi.anthropic.com, e il runner si autentica con il segreto dell'ambiente piuttosto che con OAuth interattivo.mcp-proxy.anthropic.com: le sessioni self-hosted non lo usano. Quando la consegna dei connettori è abilitata per la tua organizzazione, i connettori claude.ai della tua organizzazione raggiungono le sessioni attraversoapi.anthropic.com. Vedi Server MCP.
Questi flussi lato host raggiungono invece claude.ai, quindi eseguili da un host il cui egress lo consente piuttosto che ampliare l'egress del container della sessione:
- Il programma di installazione a una riga: recupera
install.shdaclaude.aial momento dell'installazione. claude auth logininterattivo: accede attraversoclaude.ai,claude.comeplatform.claude.com. La configurazione guidata, la modalità con accesso didoctore il dispatch da CI lo usano. Il browser con cui accedi carica anche i controlli del browser della pagina di accesso di claude.ai dahcaptcha.com,*.hcaptcha.comechallenges.cloudflare.com. Se la tua organizzazione usa il single sign-on, quel browser può anche essere reindirizzato attraversosso.claude.com.
Default-deny egress
Distribuisci container runner e sessione in un segmento di rete o namespace il cui traffico in uscita è limitato agli host nella tabella dei requisiti di rete, al tuo host git, e ai servizi interni specifici che le sessioni devono raggiungere. Il prodotto non può verificare o applicare questo, quindi applicalo al tuo confine di rete su ogni ambiente. Il codice della sessione è diretto dal modello e può tentare connessioni a host arbitrari; default-deny egress a livello di rete limita dove questi tentativi possono atterrare. Questo si applica indipendentemente dalla modalità di permesso: il set di strumenti pre-approvato predefinito include già Bash, quindi l'egress della shell viene eseguito senza un prompt anche senza modalità auto.
Per i dettagli su quale telemetria ogni sessione emette e come disattivarla, vedi Telemetria.
Autentica a un proxy di egress
Alcuni proxy di egress aziendali richiedono un'intestazione Proxy-Authorization su ogni connessione. Il token in quell'intestazione spesso ruota troppo velocemente per essere scritto nell'URL del proxy che imposti in HTTPS_PROXY. Imposta HTTPS_PROXY o HTTP_PROXY all'URL del tuo proxy come al solito, quindi imposta --proxy-authorization-command o --proxy-authorization-file per dire al runner dove leggere il valore dell'intestazione. Entrambi i flag richiedono Claude Code v2.1.238 o successivo.
Scegli da dove viene il valore Proxy-Authorization
Scegli il flag che corrisponde a come produci il token Proxy-Authorization:
--proxy-authorization-command <command>: scegli questo per un token che generi su richiesta. Il runner esegue il comando della shell e usa il suo stdout ritagliato come valore dell'intestazione, ad esempioBearer <token>.--proxy-authorization-file <path>: scegli questo per un token che un altro processo ruota in posizione. Il runner legge il file e usa i suoi contenuti ritagliati come valore dell'intestazione.
Configurazioni che il runner rifiuta di avviare con
Ogni flag ha anche una forma di variabile di ambiente, elencata accanto ad esso nel riferimento dei flag CLI del runner. Prima che il runner contatti il tuo proxy o il piano di controllo, controlla i flag e le loro variabili, e rifiuta di avviare in tre casi:
- Entrambi i flag impostati: un flag più la variabile di ambiente dell'altro flag conta come impostazione di entrambi.
- Nessun URL proxy: né
HTTPS_PROXYnéHTTP_PROXYcontiene un URLhttp://ohttps://. Il runner legge entrambe le variabili in maiuscole o minuscole, e non consultaALL_PROXY. - Uno dei flag passato al sottocomando orchestrator:
self-hosted-runner orchestratornon accetta i flag o le loro variabili di ambiente. Passa il flag a ogni runner che l'orchestrator avvia invece.
Cosa cambia il runner mentre un flag proxy-authorization è impostato
Con uno dei flag impostati, il runner avvia un listener proprio e invia il traffico proxy da se stesso, dai suoi lifecycle hook, e dalle sue sessioni attraverso quel listener. Il listener aggiunge l'intestazione Proxy-Authorization sulla strada verso il tuo proxy.
- Listener: il listener è un proxy forward su
127.0.0.1. Il runner avvia il listener prima di registrarsi con il piano di controllo, e esce all'avvio se il listener non può avviarsi. - Variabili proxy: il runner riscrive quale di
HTTPS_PROXYeHTTP_PROXYhai impostato in modo che punti al listener. Quel valore riscritto raggiunge il runner stesso, i suoi lifecycle hook, e ogni sessione che esegue. - Rotazione del token: un token ruotato ha effetto senza un riavvio. Per ogni connessione che il listener apre al tuo proxy, il runner esegue il tuo comando o legge di nuovo il tuo file e aggiunge il risultato come intestazione.
- Ambiente della sessione: una sessione raggiunge il tuo proxy solo attraverso il listener. Nell'ambiente di ogni sessione il runner rimuove
ALL_PROXY, rimuove qualsiasi ortografia diHTTPS_PROXYoHTTP_PROXYche non hai impostato, e fissaNO_PROXYal valore del runner stesso. - Log: il runner non registra mai il valore dell'intestazione.
Configura git
Il runner gestisce i checkout dei repository ma non configura l'identità git o le credenziali per impostazione predefinita. Controlli l'immagine e l'ambiente del processo del runner, quindi controlli la configurazione git. Scegli uno di due approcci:
- Lascia che il runner configuri git: avvia il runner con
--configure-gitper fargli scrivere la stessa identità e configurazione di firma dei commit che usano le sessioni ospitate da Anthropic - Spedisci la configurazione git nella tua immagine: imposta l'identità e le credenziali push tu stesso, ad esempio per eseguire il commit sotto la tua identità bot
Per i repository su github.com, puoi anche avviare il runner con --use-anthropic-git-proxy, o impostare CLAUDE_RUNNER_USE_GIT_PROXY=1, per chiedere ad Anthropic di servire git per le sessioni del runner.
Piani minimi di versione git sull'host runner: --configure-git la firma dei commit SSH richiede Git 2.34 o più recente, --use-anthropic-git-proxy richiede 2.32 o più recente, e la ripresa delle sessioni da rami spinti da --push-outcome-on-release richiede 2.29 o più recente. Git 2.24 è sufficiente se ometti tutti e tre e gestisci l'identità git tu stesso.
Lascia che il runner configuri git
Avvia il runner con --configure-git, o imposta SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, per fargli scrivere la configurazione git globale all'avvio:
user.name = Claudeeuser.email = noreply@anthropic.com, corrispondendo alle sessioni ospitate da Anthropic- Firma dei commit e dei tag in formato SSH, instradata attraverso uno shim gestito dal runner che firma ogni commit tramite il servizio di firma di Anthropic usando le credenziali della sessione stessa. Le firme sono verificabili su GitHub rispetto alla chiave di firma SSH pubblicata di Anthropic.
push.negotiate = true, in modo che git chieda al tuo host git quali commit ha già prima di impacchettare un push. Richiede Claude Code v2.1.257 o successivo.core.hooksPathche punta a una directory di hook gestita dal runner. I suoi hookcommit-msgeprepare-commit-msgaggiungono a ogni commit un trailerCo-authored-by:per il creatore della sessione. Il trailer è costruito dall'email inCCR_SESSION_ACCOUNT_EMAILe omesso quando quella variabile non è impostata. Se la tua immagine imposta giàcore.hooksPathe il runner non usa git gestito da Anthropic, il runner mantiene la tua impostazione, salta l'installazione di questi hook e stampa un avviso[runner:git].
La firma dei commit richiede git 2.34 o più recente; il runner controlla all'avvio e esce con un errore se il tuo git è più vecchio. Questo flag non configura le credenziali push, che fornisci comunque nell'immagine.
Su un runner con v2.1.280 o successivo, anche i commit che fai da un lifecycle hook checkout o post-session vengono firmati come la sessione, senza il trailer Co-authored-by:. Configurazione git all'interno dei lifecycle hook descrive le impostazioni git che il runner fissa all'interno di quegli hook.
Con o senza --configure-git, Claude Code istruisce Claude a terminare i suoi messaggi di commit con un trailer Claude-Session: <url> e le descrizioni delle sue pull request con l'URL della sessione. Per ometterli entrambi, imposta attribution.sessionUrl su false nel file ~/.claude/settings.json dell'host runner, quindi riavvia il runner.
Spedisci la configurazione git nella tua immagine
L'identità git è richiesta per qualsiasi commit. Impostala a livello di sistema nel tuo Dockerfile in modo che la configurazione si applichi indipendentemente da quale utente il processo runner esegue:
RUN git config --system user.name "Claude" && \
git config --system user.email "noreply@anthropic.com"
Senza un'identità, git commit fallisce con Please tell me who you are e le sessioni non possono fare progressi. Puoi usare la tua identità bot invece; il runner non sovrascrive questi valori.
Non incorporare credenziali push di lunga durata o ampiamente scoped in un'immagine runner condivisa: una credenziale nell'immagine è disponibile a ogni sessione che l'immagine esegue, chiunque l'abbia avviata. Invece, crea un token a breve durata, con scope minimo per sessione dal tuo script wrapper, usando l'identità del creatore della sessione decodificata dal session JWT. Abbinalo a un container per sessione effimero, che richiede --capacity 1, in modo che nessuna credenziale sopravviva alla sessione che l'ha creata; vedi la sezione hardening.
Se devi configurare le credenziali push a livello di immagine, ad esempio per una chiave di distribuzione di sola lettura, limitale il più possibile:
- Una chiave di distribuzione SSH limitata a un repository con una riscrittura
url.<base>.insteadOf - Un
credential.helperche restituisce un token con scope minimo GIT_SSH_COMMANDche punta a una chiave con scope ristretto
Qualsiasi meccanismo tu configuri deve funzionare senza un prompt, perché il clone integrato del runner e il fetch disabilitano i prompt che git, SSH, e Git Credential Manager mostrerebbero altrimenti:
- Il runner imposta
GIT_TERMINAL_PROMPT=0, in modo che git non chieda un nome utente o una password. - Il runner esegue SSH con
BatchMode=yes, aggiunto al tuoGIT_SSH_COMMANDse ne imposti uno, in modo che SSH non chieda una passphrase o una conferma dell'host. - Il runner imposta
GCM_INTERACTIVE=never, in modo che Git Credential Manager non apra una finestra di dialogo di accesso. Prima della v2.1.229, la preparazione del repository poteva bloccarsi su un prompt di Git Credential Manager. - Il runner cancella
core.askPass, quindi se usi un helper askpass, impostalo attraverso la variabile di ambienteGIT_ASKPASSinvece.
Se il tuo host git rifiuta la credenziale, o non ne hai configurata una, il runner riprova alcune volte e poi fallisce la preparazione del repository quando il repository è quello in cui la sessione spinge i risultati. Per un repository dal quale la sessione legge solo, Troubleshooting copre quando il runner lo salta invece. Il runner non passa queste impostazioni nell'ambiente della sessione.
Mantieni qualsiasi programma che nomini in GIT_SSH_COMMAND o GIT_ASKPASS dove le sessioni non possono scrivere, come la checklist hardening chiede per la directory dei hook e lo script wrapper. Lo stesso vale per qualsiasi chiave o file sulla riga di comando di quel programma. Il git del runner stesso esegue quel programma quando clona o recupera.
Se le directory di checkout sono di proprietà di un uid diverso dal processo runner, git rifiuta di operare su di esse; aggiungi safe.directory:
RUN git config --system --add safe.directory '*'
Usa il proxy git di Anthropic
Con il proxy git di Anthropic, chiamato anche git gestito da Anthropic, l'immagine del runner non ha bisogno di chiavi SSH, credential helper, .netrc o altre credenziali git per la sessione stessa. Invece, il runner chiede ad Anthropic di servire git per le sue sessioni. Per una sessione utente servita da Anthropic, il clone del runner e i fetch e i push della sessione stessa passano attraverso Anthropic, che usa il token OAuth di GitHub memorizzato per il creatore della sessione. Come Anthropic serve git per una sessione tratta le sessioni bot e agente.
Il proxy git è disattivato a meno che tu non lo attivi. Un runner che raggiunge il tuo host git con le proprie credenziali non ne ha bisogno, e il suo git funziona con qualsiasi host git.
In cambio, il proxy git limita ciò che il runner supporta e cambia ciò di cui ha bisogno:
-
Solo github.com: Anthropic serve una sessione solo quando tutti i suoi repository sono su github.com, e il proxy git non supporta ancora GitHub Enterprise Server. Su un runner con il proxy git, una sessione con un repository su un altro host git non riesce ad avviarsi.
-
Credenziali solo per i repository della sessione: Anthropic fornisce le credenziali git per i repository che fanno parte della sessione, non per altri repository sullo stesso host git. Un submodule privato, una dipendenza che il tuo package manager recupera con git o un marketplace di plugin in un altro repository non ricevono alcuna credenziale da Anthropic. Chiedi alle persone che creano le sessioni di aggiungere ogni repository di cui una sessione ha bisogno al momento della creazione.
Ogni esecuzione di un'attività pianificata nel cloud è una sessione con i repository dell'attività, quindi chiedi al proprietario di ciascuna attività di aggiungere ogni repository di cui le sue esecuzioni hanno bisogno.
-
Solo push di branch: un push che elimina un branch fallisce, così come un push verso qualsiasi altro tipo di ref, come un tag. Per sapere quali branch un push può aggiornare, vedi Proxy GitHub.
-
Account GitHub collegati: chiedi a ogni persona che crea sessioni utente con un repository di collegare GitHub. Senza un proprio collegamento, quelle sessioni non si avviano.
-
--capacity 1: il proxy git richiede una sessione per processo runner, quindi esegui più repliche per il parallelismo. Attiva il proxy git di Anthropic elenca i requisiti. -
Configurazione git globale sostituita: il runner elimina e sostituisce la configurazione git globale dell'utente con cui viene eseguito. Eseguilo come utente dedicato o in un container.
-
Credenziali dell'host per i push dell'host: il push di
--push-outcome-on-releasedel runner e qualsiasi push effettuato dal tuo hookpost-sessionusano comunque le credenziali git dell'host runner e il suo percorso di rete versogithub.com. Tieni quelle credenziali fuori dalla configurazione git globale e vedi Spedisci la configurazione git nella tua immagine per i modi in cui fornirle. -
Decisione per sessione: Anthropic decide per ogni sessione sul runner se servire il suo git, e una sessione che non serve non riesce ad avviarsi. Quando le sessioni non riescono ad avviarsi su un runner con il proxy git ne illustra le cause.
[!WARNING]
Con
--use-anthropic-git-proxyimpostato, il runner elimina e sostituisce la configurazione git globale dell'utente con cui viene eseguito, senza conservarne un backup. Lo fa all'avvio e prima di ogni sessione. Un login o un credential helper che tenevi lì va perso. Le impostazioni scritte da--configure-gitvengono mantenute. Esegui il runner come utente dedicato o in un container, mai con il tuo utente.
Mantieni le impostazioni git non segrete, come l'identità e safe.directory, nella configurazione git di sistema.
Attiva il proxy git di Anthropic
Prima di avviare il runner con --use-anthropic-git-proxy, verifica che l'host runner soddisfi ciascuno di questi requisiti. Il runner rifiuta di avviarsi quando il requisito di capacità o quello di git non è soddisfatto:
- Claude Code v2.1.267 o successivo: le versioni precedenti accettano il flag ma non segnalano la richiesta ad Anthropic di servire git né stampano la riga
Registering as opted in, quindi Anthropic non serve le loro sessioni. --capacity 1, il valore predefinito: ogni processo runner gestisce una sessione alla volta, quindi esegui più repliche per il parallelismo.- Git 2.32 o più recente: git più vecchio ignora la configurazione git per sessione che il runner imposta per il proxy git.
[!WARNING]
Le ricette Kubernetes e Docker Compose su questa pagina usano
--capacity 4. Se aggiungi--use-anthropic-git-proxyoCLAUDE_RUNNER_USE_GIT_PROXY=1a una di esse senza cambiare la capacità a1, il runner esce all'avvio ogni volta che il tuo orchestrator lo riavvia. Imposta--capacity 1ed esegui più repliche per il parallelismo. Quando il runner esce mostra la riga che il runner stampa.
Per attivare il proxy git, aggiungi --use-anthropic-git-proxy al comando del runner, oppure imposta CLAUDE_RUNNER_USE_GIT_PROXY=1 nell'ambiente del runner. Questo comando, eseguito in una shell sull'host runner, avvia il runner della guida rapida con il proxy git attivo:
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxy
All'avvio, il runner stampa Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). Anthropic decide quindi per ogni sessione su quel runner se servire il suo git. Per ogni sessione che serve, il runner registra una riga [runner:session] contenente governed git ACTIVE. Se invece una sessione non riesce ad avviarsi, vedi Quando le sessioni non riescono ad avviarsi su un runner con il proxy git.
Come Anthropic serve git per una sessione
Per una sessione servita da Anthropic, il clone del runner e i fetch e i push della sessione stessa passano attraverso Anthropic, autenticati con il token a breve durata della sessione stessa:
- Sessioni utente: Anthropic usa il token OAuth di GitHub memorizzato per il creatore della sessione.
- Sessioni bot e agente: Anthropic usa il token di installazione della GitHub App della tua organizzazione.
- Riscritture degli URL:
--git-host-rewritee--git-ssh-rewritenon hanno effetto su un repository servito dal proxy git.
Quando le sessioni non riescono ad avviarsi su un runner con il proxy git
Su un runner avviato con --use-anthropic-git-proxy, una sessione non riesce ad avviarsi quando Anthropic non serve il suo git. Cerca nel log del runner un errore git che nomina un indirizzo api.anthropic.com contenente /git_proxy/.
Per ogni sessione, un runner con Claude Code v2.1.267 o successivo registra anche una riga [runner:session] contenente governed git ACTIVE quando Anthropic serve il git della sessione, oppure una riga [runner:warn] contenente the server withheld Anthropic-managed git for this session quando non lo fa. Trova la riga che vedi tra questi casi:
- Né
governed git ACTIVEné la rigawithheld: un runner precedente a Claude Code v2.1.267 non registra nessuna delle due righe, e Anthropic non serve le sue sessioni. Aggiorna il runner a v2.1.267 o successivo seguendo Fissa la versione. - La riga
withheld: Anthropic non ha servito la sessione. Un runner che prima funzionava con il proxy git può fallire in questo modo senza alcuna modifica da parte tua.- Un repository non è su github.com: una sessione con anche un solo repository su un altro host git, come GitHub Enterprise Server, non viene servita, compresi i suoi repository su github.com. Disattiva il proxy git di Anthropic per i runner di quell'ambiente.
- Tutti i repository sono su github.com: segnala il problema al tuo team di account Anthropic con l'ID della sessione dalla riga
withheld. Anthropic registra il motivo dal proprio lato.
- Una riga contenente
remote: access denied by the git proxy: una sessione servita da Anthropic può comunque essere rifiutata, ad esempio quando la policy dell'organizzazione nega l'accesso git per la sessione, o la sessione non è autorizzata per il repository. Il log del runner mostra allora una riga contenenteremote: access denied by the git proxy, e il resto di quella riga ne indica il motivo. GitHub authentication required: compare quando il creatore della sessione non ha una connessione GitHub funzionante su claude.ai. Il clone della sessione fallisce e l'errore git riportaGitHub authentication required. Please reconnect your GitHub account.Chiedi a quella persona di collegare o ricollegare GitHub nelle sue impostazioni di claude.ai.
Dopo aver corretto la causa, avvia di nuovo le sessioni non riuscite.
Disattiva il proxy git di Anthropic
Se le sessioni in un ambiente usano un repository su un host git diverso da github.com, come GitHub Enterprise Server, disattiva --use-anthropic-git-proxy per i runner di quell'ambiente.
1. Rimuovi il flag
Rimuovi --use-anthropic-git-proxy dal comando del runner. Se hai impostato CLAUDE_RUNNER_USE_GIT_PROXY nell'ambiente del runner, ad esempio in una pod spec o in un file Compose, rimuovila da lì. In una shell, annulla la sua impostazione:
unset CLAUDE_RUNNER_USE_GIT_PROXY
2. Fornisci al runner le credenziali git
Fornisci credenziali che funzionino senza un prompt per ogni host git usato dalle sessioni dei runner, github.com compreso. Qualsiasi credenziale presente nella configurazione git globale dell'utente del runner non c'è più, perché il runner ha eliminato quella configurazione mentre --use-anthropic-git-proxy era impostato. Spedisci le credenziali nella tua immagine o usa un lifecycle hook checkout.
3. Apri il percorso di rete
Consenti al runner di raggiungere ogni host git usato dalle sessioni dei runner sulla porta 443 o 22. Vedi la riga dell'host git in Requisiti di rete.
4. Riavvia i runner
Riavvia i runner in modo che si registrino senza il proxy git. Quindi avvia di nuovo ogni sessione non riuscita.
Accesso all'API di GitHub senza la GitHub CLI
Se l'immagine del tuo runner non include la GitHub CLI, Claude Code può fornire un gh integrato, così Claude può comunque aprire pull request, commentare e leggere i risultati della CI. Il gh integrato è pensato per i runner che usano git gestito da Anthropic. Supporta un solo comando, gh api, che chiama la REST API di GitHub. Richiede Claude Code v2.1.287 o successivo nell'immagine del runner.
Questo comando apre una pull request al posto di gh pr create. Il gh integrato compila {owner} e {repo} per il repository corrente:
gh api repos/{owner}/{repo}/pulls -f title='Fix' -f head='my-branch' -f base='main'
- Credenziali: il
ghintegrato invia le sue richieste REST attraverso git gestito da Anthropic, che fornisce la credenziale GitHub dal lato di Anthropic, quindi l'immagine non ha bisogno di alcun token GitHub per esso - Quali sessioni lo ottengono: Anthropic decide per ogni sessione se git gestito da Anthropic serve il
ghdella sessione. Quando lo fa, la riga[runner:session] governed git ACTIVEche il runner registra per la sessione mostragh_path_shim=true. Quando non lo fa, la sessione non hagh jq: installajqnell'immagine se vuoi che--jqfunzioniCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: se l'ambiente della sessione la imposta, Claude Code non fornisce ilghintegrato e la sessione non hagh
Accesso all'API di GitHub con la GitHub CLI
Se l'immagine del tuo runner include la GitHub CLI da far usare alle sessioni, fai in modo che il tuo script wrapper crei per ogni sessione un GH_TOKEN a breve durata e con scope minimo per il creatore della sessione. Quando gh si trova nel PATH con cui Claude Code si avvia, la sessione ha quel gh e nessun gh integrato. Con il token, quel gh raggiunge GitHub con qualunque accesso il token conceda e al di fuori di git gestito da Anthropic, quindi consenti api.github.com.
Con git gestito da Anthropic, lascia la GitHub CLI fuori dall'immagine per usare invece il gh integrato.
Affida un'autorità di certificazione privata a git gestito da Anthropic
Questa sezione si applica se imposti GIT_SSL_CAINFO o GIT_SSL_NO_VERIFY nell'ambiente di un runner le cui sessioni usano git gestito da Anthropic. La gestione che descrive richiede che il runner esegua Claude Code v2.1.283 o successivo.
Quando git sul runner deve fidarsi di un'autorità di certificazione (CA) privata, come quella con cui un proxy che ispeziona TLS firma, gli approcci usuali funzionano come segue:
- Archivio certificati di sistema: installa la tua CA nell'archivio certificati di sistema dell'host runner, e git si fida di essa senza nessuna delle due variabili.
GIT_SSL_CAINFO: impostala a un file PEM dei tuoi CA, ad esempioGIT_SSL_CAINFO=/etc/ssl/corp-ca.pem.GIT_SSL_NO_VERIFY: non aiuta dietro un proxy che firma di nuovo. Il clone del runner stesso attraverso git gestito da Anthropic controlla i certificati anche quando la variabile è impostata, quindi quel clone fallisce finché git non si fida della tua CA attraverso uno degli altri due approcci.
Per le connessioni git che portano il token di una sessione a git gestito da Anthropic, il runner applica le due variabili come segue. Un command hook inizia con l'ambiente della sessione, quindi ottiene quello che git dentro la sessione ottiene:
GIT_SSL_CAINFO: quello che git controlla git gestito da Anthropic dipende da dove git viene eseguito:- Clone e fetch del runner stesso: vengono eseguiti senza la variabile e controllano git gestito da Anthropic rispetto a un file di certificato per sessione che il runner scrive. Il runner crea quel file a partire dal bundle CA di sistema dell'host runner e dai certificati presenti nel tuo file.
- Git dentro la sessione: ottiene la configurazione
http.sslCAInfoche nomina il tuo file al posto della variabile, più vocihttp.<url>.sslCAInfoche controllano git gestito da Anthropic rispetto al file per sessione. - Hook
checkoutepost-session: ereditano la variabile invariata.
GIT_SSL_NO_VERIFY: quali controlli di certificato rimangono spenti dipende da dove git viene eseguito:- Clone e fetch del runner stesso: vengono eseguiti senza la variabile e controllano il certificato che gli viene presentato.
- Git dentro la sessione: ottiene la configurazione
http.sslVerify=falseal posto della variabile, quindi i controlli rimangono spenti per altri host. Ottiene anche vocihttp.<url>.sslVerify=trueche mantengono i controlli accesi per git gestito da Anthropic. - Hook
checkoutepost-session: quando la sessione ha un repository su git gestito da Anthropic, ottengono la configurazionehttp.sslVerify=falseal posto della variabile. Ottengono anche vocihttp.<url>.sslVerify=trueche mantengono i controlli accesi per git gestito da Anthropic.
Il file di certificato per sessione ha bisogno di un bundle CA di sistema a /etc/ssl/certs/ca-certificates.crt o /etc/pki/tls/certs/ca-bundle.crt sull'host runner. Ha anche bisogno di un file GIT_SSL_CAINFO che l'utente del runner può leggere, che contiene blocchi PEM CERTIFICATE, e che è al massimo 1 MiB. Quando il runner non può costruire il file per sessione, registra una riga [runner:warn] contenente did not build the certificate file e il motivo. Git quindi usa il tuo file come è per git gestito da Anthropic. Correggi quello che la riga nomina.
Per ogni sessione che usa git gestito da Anthropic, il runner registra anche una riga [runner:warn] che inizia con governed git: GIT_SSL_CAINFO is set o governed git: GIT_SSL_NO_VERIFY is set. La riga dice quello che il runner ha fatto con quella variabile per il suo git, per git dentro la sessione, e per i tuoi lifecycle hook. Termina con se hai bisogno di cambiare qualcosa.
Aggiungi repository github.com alle sessioni dei canali Claude Tag
Se Claude non riesce ad aggiungere un repository github.com a una sessione di un canale Claude Tag, assicurati che il canale vi abbia accesso, anche quando il tuo runner riesce a clonarlo. Quell'accesso deriva dalle impostazioni su GitHub e nella tua organizzazione Claude, non dalle credenziali git del tuo runner.
1. Installa la Claude GitHub App
Installa la Claude GitHub App sull'organizzazione o sull'account GitHub proprietario del repository e includi quel repository nell'installazione.
2. Collega l'organizzazione o l'account GitHub
In Organization settings > Git providers, collega quell'organizzazione o quell'account alla tua organizzazione Claude. Può collegarla un Owner della tua organizzazione Claude che sia anche proprietario dell'organizzazione GitHub. Vedi Collegare la tua organizzazione GitHub.
3. Concedi il repository
Nelle impostazioni di amministrazione di Claude Tag, concedi il repository al canale o a un ambito più ampio che includa il canale: il suo workspace Slack o tutto Slack. Può concederlo chi ha il ruolo Owner o il permesso Claude Tag Admin. Anche un gestore del canale può aggiungerlo al proprio canale se il suo account GitHub è amministratore del repository. Vedi Concedere l'accesso ai repository.
Quindi avvia un nuovo thread nel canale e nomina il repository nel tuo primo messaggio.
Se Claude non riesce ancora ad aggiungere il repository, vedi Se Claude non riesce a raggiungere un repository per sapere dove controllare il collegamento GitHub e la concessione del repository.
Riscrivi gli URL git per le reti private
Gli URL dei repository arrivano dal piano di controllo come HTTPS, con il nome host del tuo host git; per GitHub Enterprise, questo è il nome host che hai configurato per l'integrazione GitHub Enterprise su claude.ai. Due flag ripetibili riscrivono quegli URL prima del clone:
--git-host-rewrite <from>=<to>: per split-horizon DNS, dove Anthropic raggiunge il tuo host git tramite un nome host esterno ma i runner devono usarne uno interno--git-ssh-rewrite <host>: per host git che accettano solo SSH, o per un host runner che ha solo una chiave SSH. Ad esempio, per clonare i repository di github.com tramite SSH, aggiungi--git-ssh-rewrite github.comal comando del runner. Il runner riscrive quindihttps://github.com/owner/repoingit@github.com:owner/repo. SSH deve connettersi senza un prompt.
La riscrittura dell'host viene eseguita per prima, quindi elenca il nome host interno in --git-ssh-rewrite se hai bisogno di entrambi. Per il controllo completo del checkout, usa un checkout lifecycle hook.
Avvia una sessione su un repository esterno a GitHub
Per eseguire una sessione su un repository di un host git diverso da GitHub, avvia la sessione dal tuo terminale, in un checkout di quel repository. Prima controlla le credenziali git del runner, la rete e il proxy git, l'indirizzo del repository e il tuo branch:
- Credenziali git e rete: fornisci credenziali che funzionino senza un prompt per
https://<host>/<owner>/<name>, l'indirizzo che il runner clona, e consenti al runner di raggiungere il tuo host git, come elencato in Requisiti di rete. Per un host git che accetta solo SSH, vedi--git-ssh-rewrite. - Proxy git: se i runner dell'ambiente usano il proxy git di Anthropic, disattivalo.
- Indirizzo del repository: controlla il remote
origindel checkout. Il suo host deve essere un nome di dominio comegit.example.com, e il suo percorso deve avere la forma<owner>/<name>. Un percorso annidato comegroup/subgroup/namenon è accettato. - Branch: esegui il push del branch su cui ti trovi al tuo host git, perché il runner clona quel branch.
Nel checkout, dopo aver effettuato l'accesso a claude.ai come descritto in Autenticarsi dalla CI, esegui claude -p con --environment e l'ID ccpool_... del tuo ambiente:
claude -p "Fix the login bug" --environment ccpool_abc123 --output-format json
Quando la sessione viene creata, il comando stampa una riga di JSON che inizia con {"ok":true. Se il remote origin non corrisponde ai requisiti, il comando fallisce con requires a git source, but no GitHub remote was detected.
Esamina ed esegui il merge sul tuo host git dei branch di cui le sessioni eseguono il push.
Costruisci l'immagine del runner
Anthropic non pubblica un'immagine runner pre-costruita. Costruisci la tua intorno al binario claude, stratificando qualsiasi toolchain di cui i tuoi repository hanno bisogno: runtime di linguaggio, compilatori, gestori di pacchetti, e sidecar MCP.
Le ricette di seguito usano --capacity 4, in modo che un container serva fino a quattro sessioni concorrenti dallo stesso owner bloccato. Questo non fornisce l'isolamento del container per sessione nella sezione hardening: prima di connettere un ambiente ai sistemi di produzione, esegui le ricette a --capacity 1 con un container per sessione, o usa runner on-demand, che mantengono anche il segreto dell'ambiente lontano dagli host che eseguono sessioni. Se aggiungi il proxy git Anthropic a una di queste ricette, cambia anche --capacity a 1.
Questo Dockerfile è un punto di partenza minimo. Installa python3 perché gli hook che il control plane di Anthropic fornisce alle sessioni Claude Tag, incluso quello che fa pubblicare a Claude la sua risposta in Slack, eseguono python3. Mantieni python3 nel PATH in qualsiasi immagine che serve sessioni Claude Tag:
FROM debian:bookworm-slim
ARG CLAUDE_CODE_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates openssh-client jq python3 \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_CODE_VERSION:?set with --build-arg CLAUDE_CODE_VERSION}/linux-x64/claude" \
-o /usr/local/bin/claude && chmod +x /usr/local/bin/claude
RUN git config --system user.name "Claude" \
&& git config --system user.email "noreply@anthropic.com" \
&& git config --system --add safe.directory '*'
ENTRYPOINT ["claude"]
Scambia linux-x64 con linux-arm64 se i tuoi nodi sono ARM, o con linux-x64-musl o linux-arm64-musl su un'immagine basata su musl come Alpine; vedi Configurazione Alpine Linux per i pacchetti extra di cui le immagini musl hanno bisogno. L'URL è la posizione di rilascio standard di Claude Code, quindi puoi verificare il binario scaricato rispetto al manifesto firmato del rilascio come descritto in Integrità binaria e firma del codice. Il runner richiede Claude Code versione 2.1.224 o successiva, oppure 2.1.229 o successiva per servire sessioni Claude Tag. Esegui la build dell'immagine, quindi spingila al tuo registro e fai riferimento ad essa nelle ricette di seguito:
docker build \
--build-arg CLAUDE_CODE_VERSION="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/stable)" \
-t <your-registry>/claude-runner:latest .
La sostituzione di comando cerca il numero di rilascio stable corrente e lo passa come argomento di build, quindi eseguire lo stesso comando dopo un nuovo rilascio stabile ricostruisce il livello di download con il binario più recente. Per fissare un rilascio specifico per build riproducibili, passa il numero di versione direttamente come CLAUDE_CODE_VERSION. Sostituisci stable con latest nell'URL di ricerca quando hai bisogno di un rilascio più recente del canale stabile, come uno che un modello appena lanciato richiede.
Dimensiona CPU e memoria per le sessioni
Dimensiona il container o l'host di un runner per le sessioni che esegue piuttosto che per il processo runner. Il runner stesso esegue il polling per il lavoro, prepara il checkout di ogni sessione, esegue i tuoi lifecycle hook, e avvia e supervisiona i processi della sessione. Il carico proviene dalle sessioni: ognuna è un processo Claude Code più tutto ciò che avvia, come build, suite di test, installazioni di pacchetti, e server MCP.
Per una sessione, inizia con i seguenti valori, indicati come richieste e limiti di Kubernetes o l'equivalente della tua piattaforma, e trattali come un punto di partenza piuttosto che un requisito:
- Memoria: una richiesta e un limite di 4 GiB ciascuno, che soddisfa il minimo di 4 GB nei requisiti di sistema di Claude Code. Mantieni i due uguali in modo che lo scheduler conti la memoria completa del container. Quando il container raggiunge il suo limite di memoria, il kernel uccide i processi al suo interno, il che può terminare una sessione a metà compito.
- CPU: una richiesta di 2 CPU e un limite di 4 CPU, in modo che una sessione possa scoppiare sopra la richiesta durante le build. Il kernel limita un container al suo limite di CPU piuttosto che uccidere i processi al suo interno, quindi le sessioni al limite vengono eseguite più lentamente ma continuano a funzionare.
In una specifica di container Kubernetes, imposta quei valori iniziali con il seguente blocco resources:
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 4Gi
Le build e i test sono solitamente la parte più grande e più variabile del carico di una sessione, quindi esegui una build rappresentativa del tuo repository, misura il suo picco di CPU e memoria, e aumenta qualsiasi valore iniziale che non lascia spazio per il processo Claude Code in cima a quel picco.
Il runner usa --capacity per limitare quante sessioni esegue contemporaneamente. Non divide CPU o memoria tra di loro, quindi le sessioni su un runner condividono la CPU e la memoria del container. Per evitare che l'uso di memoria di una sessione termini le altre, esegui una sessione per runner con il proprio limite di memoria.
-
Una sessione per runner: dai a ogni container i valori di una sessione. Usa questo dimensionamento a
--capacity 1, che la sezione hardening consiglia, e per runner on-demand, dove imposti i valori sul carico di lavoro che il tuo hookspawn-runnerinvia, come il modello di pod di un Kubernetes Job. -
Diverse sessioni per runner: a un
--capacitysopra uno, moltiplica i valori di una sessione per la capacità, perché fino a quel numero di sessioni possono essere eseguite nel container contemporaneamente. Le ricette Kubernetes e Docker Compose eseguono--capacity 4senza limiti di CPU o memoria, quindi aggiungi limiti dimensionati per la capacità che esegui. -
Diversi container runner su un host: dimensiona l'host per la somma delle capacità dei runner, perché ogni runner conta solo le proprie sessioni rispetto a
--capacity:- Memoria: moltiplica la memoria di una sessione per la somma, quindi aggiungi ciò che usano i processi propri dell'host
- CPU: moltiplica la richiesta di CPU di una sessione per la somma. Per permettere a ogni sessione di superare temporaneamente la richiesta contemporaneamente, moltiplica invece il suo limite di CPU.
Dai a ogni runner il proprio container. Usa lo stesso percorso
--base-dirin ciascuno, su uno storage che nessun altro container runner monta, in modo che ogni runner abbia la propria directory di lavoro.
Dai a ogni sessione il proprio limite di memoria
Per evitare che una sessione termini le altre quando la memoria si esaurisce, esegui ogni runner a --capacity 1 sotto il proprio limite di memoria, impostato sulla memoria di una sessione:
- In un container: esegui un runner per container e imposta il limite di memoria del container, come
limits.memorysu Kubernetes,--memorysu Docker, omem_limitin un file Compose. Mantieni la somma dei limiti dei container al di sotto della memoria dell'host, perché quando è l'host stesso a esaurirla, il kernel può uccidere processi ovunque sull'host. - Sotto systemd senza un container: imposta
MemoryMaxsull'unità del runner ed esegui un runner per host, perché i runner che usano lo stesso--base-dirsu un filesystem condividono i repository clonati lì. Ad esempio, a--capacity 1senza hookcheckout, le loro sessioni su un repository possono lavorare nello stesso checkout contemporaneamente. Per eseguire diversi runner su un host, dai a ciascuno un container.
Quando un container o un'unità raggiunge il proprio limite di memoria, il kernel uccide i processi solo al suo interno. Se imposti --capacity sopra uno, le sessioni condividono un unico limite. Quando lo raggiungono, il kernel non sceglie per sessione quali processi uccidere, quindi l'uso di memoria di una sessione può terminare le altre.
Su Kubernetes, non puoi separare le sessioni con nessuno dei due valori dell'impostazione singleProcessOOMKill del kubelet. Non puoi nemmeno limitare la memoria totale di una sessione con un ulimit di memoria nel tuo script wrapper. Il kernel applica un valore come ulimit -v a ogni processo separatamente.
Lascia spazio sotto i limiti di processi e thread
Per dimensionare un limite di processi e thread, misura ciò di cui ha bisogno una sessione e moltiplicalo per il numero di sessioni che condividono il limite. Quando quelle sessioni raggiungono il limite, il kernel rifiuta nuovi processi e thread, quindi comandi, build e test in ognuna di esse possono fallire. La chiamata rifiutata restituisce EAGAIN, il cui messaggio standard è Resource temporarily unavailable.
Supponi che una sessione raggiunga un picco di 300 processi e thread, e che quattro sessioni condividano il limite. Insieme ne richiedono 1.200, e con un limite di 2.400 lasci spazio per build che ne richiedono il doppio.
Dove impostare ciascun limite
Ogni limite in questa tabella conta i thread oltre ai processi, e pids.max è il file che contiene il limite di un cgroup:
| Limite | Dove lo imposti | Cosa lo condivide |
|---|---|---|
pids.max su Kubernetes |
podPidsLimit nella configurazione del kubelet dei nodi che eseguono i runner. Non puoi impostarlo in una specifica di Pod. |
I container di un Pod |
pids.max su Docker |
--pids-limit, o pids_limit in un file Compose |
Un container |
pids.max sotto systemd |
TasksMax sull'unità del runner |
Un'unità |
ulimit -u |
La shell o il servizio che avvia il runner | Tutto ciò che sull'host viene eseguito con l'ID utente del runner. Non applicato per root. |
Quando non imposti TasksMax, systemd applica DefaultTasksMax, che per impostazione predefinita è il 15% del valore più basso tra kernel.pid_max, kernel.threads-max e il pids.max del cgroup root.
Misura il picco di una sessione
Per ottenere i numeri per i tuoi runner, misura una sessione sul suo host, che su Kubernetes è il nodo:
1. Avvia un runner da solo
Avvia un runner a --capacity 1 senza nient'altro nel suo container o nella sua unità systemd, da solo sul suo host e nel suo ambiente:
- Il suo host: usane uno che non esegue nessun altro runner. Potrai così individuare il suo processo nel passaggio successivo.
- Il suo ambiente: rendilo l'unico runner lì, ad esempio in un ambiente che crei per il test. Le sessioni vengono instradate a un ambiente e non a un runner.
Non avviare ancora una sessione, perché ti serve la directory del passaggio successivo prima che la build inizi.
2. Trova la directory cgroup del runner
Sull'host, non all'interno del container, trova l'ID del processo del runner, ad esempio con ps. Leggi /proc/<pid>/cgroup per quell'ID:
- Una riga contiene
:pids:: l'host usa cgroup v1, e la directory è/sys/fs/cgroup/pidsseguita dal testo dopo:pids: - L'unica riga inizia con
0::: l'host usa cgroup v2, e la directory è/sys/fs/cgroupseguita dal testo dopo0::
3. Esegui una build rappresentativa e registra il picco
Esegui una sessione attraverso una build rappresentativa e rileva il picco dalla directory cgroup del runner:
- La directory ha
pids.peak: leggi il file al termine della build - La directory non ha
pids.peak: leggipids.currentuna volta al secondo mentre la build è in esecuzione e conserva il valore più alto. Un picco tra due letture passa inosservato, quindi considera quel valore come una stima per difetto.
4. Moltiplica per le sessioni che condividono il limite
- Il Pod, il container o l'unità di un runner: moltiplica il picco per il
--capacitydel runner - Un limite condiviso da diversi runner: moltiplica il picco per la somma delle loro capacità
Poi aggiungi il picco di qualsiasi altra cosa che condivide il limite, come un altro container nel Pod.
5. Imposta il limite al di sopra del risultato
Con un limite più alto lasci spazio per build più pesanti, e con uno più basso fermi prima una fork bomb.
Controlla un limite pids.max
- Verifica se
pids.maxè stato raggiunto: cerca nel log del kernel dell'host, ad esempio condmesg, una riga che contienecgroup: fork rejected by pids controller ine il percorso di un cgroup. Il kernel lo registra solo la prima volta che rifiuta un processo o un thread in quel cgroup, quindi cerca nell'intero log. - Visualizza il limite in vigore: leggi
pids.maxnella directory cgroup del runner e in ogni directory al di sopra di essa. Il valore più basso è il limite in vigore, emaxsignifica nessun limite a quel livello.
Kubernetes
Il runner espone GET /healthz sulla porta 8080 per impostazione predefinita, configurabile con --health-port, quindi le probe di Kubernetes funzionano senza alcuna configurazione aggiuntiva. L'endpoint restituisce 200 ogni volta che il processo è attivo, quindi le probe seguenti rilevano un processo terminato, non uno bloccato; per individuare un runner che ha smesso di eseguire il polling, imposta un avviso sulla serie last_poll_age_seconds da /metrics. Il Deployment seguente monta il secret dell'ambiente da un Secret di Kubernetes, punta le probe di liveness e readiness a /healthz e imposta un periodo di tolleranza per la terminazione di 90 secondi. Consulta Tempistiche di arresto per capire perché il periodo di tolleranza è importante.
Il manifest non imposta resources di CPU o memoria sul container del runner. Aggiungi un blocco dimensionato per la capacità che esegui, come descritto in Dimensionare CPU e memoria per le sessioni.
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-runner
namespace: claude-runners
spec:
replicas: 3
selector:
matchLabels:
app: claude-runner
template:
metadata:
labels:
app: claude-runner
app.kubernetes.io/part-of: claude-code-self-hosted-runner
spec:
terminationGracePeriodSeconds: 90
containers:
- name: runner
image: <your-registry>/claude-runner:latest
args:
- self-hosted-runner
- --environment-secret-file
- /etc/claude/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
volumeMounts:
- name: environment-secret
mountPath: /etc/claude
readOnly: true
ports:
- name: health
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: environment-secret
secret:
secretName: claude-runner-environment-secret
Il Deployment precedente si trova in un namespace claude-runners. Crea prima il namespace:
kubectl create namespace claude-runners
Crea il Secret sottostante a partire da un file locale contenente il valore che hai copiato nel passaggio Copy environment key dell'interfaccia di amministrazione, in modo che il secret non compaia mai nella cronologia della shell. Esegui (umask 077 && cat > ./environment-secret), incolla il secret, premi Enter, poi Ctrl-D. Quindi crea il Secret ed elimina il file:
kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret
Docker Compose
Il servizio Compose di seguito riavvia il runner ogni volta che esce, il che copre sia i crash che l'uscita normale dopo il drenaggio. Una politica di riavvio di Docker riavvia lo stesso container con il suo strato scrivibile intatto, quindi il runner torna su un filesystem riutilizzato piuttosto che su uno fresco che la postura hardening consiglia; usa questa ricetta per la valutazione, e per la produzione ricrea il container per esecuzione o usa un orchestrator che lo fa.
Docker attende più a lungo prima di ogni riavvio di un container che continua a uscire, fino a un limite massimo, quindi un runner che non riesce ad avviarsi non continua a riavviarsi in un ciclo stretto secondo questa ricetta. Quando il runner esce descrive cosa controllare quando ciò accade.
services:
claude-runner:
image: <your-registry>/claude-runner:latest
command:
- self-hosted-runner
- --environment-secret-file
- /run/secrets/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
secrets:
- environment-secret
restart: always
stop_grace_period: 90s
secrets:
environment-secret:
file: ./environment-secret
Shutdown timing
[!NOTE]
Questi tempi si applicano ai runner. Il processo
claude self-hosted-runner orchestratorha i propri: consulta Size--shutdown-drain-secand the orchestrator's stop timeout.
All'avvio, ogni runner registra nei log quanto tempo può servirgli dopo SIGTERM per chiudere le sue sessioni in modo pulito, ad esempio This runner needs up to 80s con le impostazioni predefinite. Il timeout di arresto è il tempo che il tuo supervisore dei processi attende dopo SIGTERM prima di terminare il runner. Impostalo ad almeno il tempo registrato nei log:
- Kubernetes:
terminationGracePeriodSeconds. Il valore predefinito di Kubernetes è 30 secondi, quindi senza questa impostazione può fermare il pod prima che il runner finisca. - Docker Compose:
stop_grace_period
Su SIGTERM, il runner smette di accettare nuove sessioni. Poi avvia un arresto controllato, chiamato drenaggio, a meno che tu non imposti --defer-shutdown-max-min per ritardarlo. Il drenaggio ha tre passaggi:
- Il runner aspetta fino a
--drain-wait-secsecondi,0per impostazione predefinita, affinché i turni ancora in esecuzione finiscano. - Termina il process tree di ogni sessione, incluso qualsiasi comando che Claude stava ancora eseguendo, ma non un processo ancora in esecuzione dopo che il suo comando shell è terminato.
- Esegue il
post-sessionlifecycle hook.
Il runner continua a eseguire il polling verso Anthropic per tutta la durata del drenaggio. Questo mantiene le sue sessioni assegnate a esso, così un altro runner non ne prende una mentre l'hook post-session sta ancora salvando il lavoro non committato.
Il tempo registrato nei log è la somma di questi valori:
--drain-wait-secper il passaggio 1, 0 secondi per impostazione predefinita--session-stop-grace-secper il passaggio 2, 5 secondi per impostazione predefinita--post-session-hook-timeout-secper il passaggio 3, 60 secondi per impostazione predefinita- Un margine fisso di 15 secondi
- 30 secondi in più quando
--push-outcome-on-releaseè impostato
Con le impostazioni predefinite il totale è 0 + 5 + 60 + 15 = 80 secondi. Un valore più alto di --capacity non lo aumenta, perché il runner drena tutte le sue sessioni contemporaneamente.
Concedi più tempo se imposti uno di questi flag:
- Con
--retire-at: lascia abbastanza tempo tra il momento del ritiro e il momento in cui l'host si ferma perché i turni tipici finiscano, più l'attesa per le attività in background descritta in Runner lifecycle, più il tempo registrato nei log. Calcola il momento del ritiro a ogni avvio, ad esempiodate +%spiù la durata prevista del runner. - Con
--defer-shutdown-max-min: il timeout di arresto deve coprire anche i minuti che configuri e un'ulteriore attesa prima che il drenaggio inizi, 75 secondi con le impostazioni predefinite. Defer the drain past the first signal spiega entrambi. Il runner registra nei log anche questo tempo più lungo all'avvio.
Lascia finire i turni in esecuzione prima di un riavvio
Per lasciare che i turni in esecuzione finiscano prima di un riavvio rolling, imposta --drain-wait-sec e aumenta il timeout di arresto ad almeno quell'attesa più 80 secondi. Con l'attesa predefinita di 0, un riavvio interrompe qualsiasi turno ancora in esecuzione, e la sessione riprende su un altro runner senza il suo lavoro non pushato.
--drain-wait-sec: all'incirca la durata dei turni e delle attività in background che vuoi lasciar finire. È un massimo: l'attesa termina prima se finiscono prima.- Il tuo timeout di arresto:
terminationGracePeriodSecondsin Kubernetes,stop_grace_periodin Docker Compose. Ad esempio, con--drain-wait-sec 120, sostituisci il90nella ricetta Kubernetes o Docker Compose con200. - Se hai modificato altre impostazioni di arresto: gli 80 secondi valgono per le impostazioni predefinite. Avvia un runner con i tuoi flag e usa almeno il numero nella sua riga di avvio
This runner needs up to, che include l'attesa. Con--defer-shutdown-max-minimpostato, usa il numero più grande nella riga di avvio successiva, dopoSize the stop timeout to at least. Shutdown timing elenca cosa si aggiunge a questi numeri.
[!WARNING]
Se il timeout di arresto scade durante l'attesa, il tuo host termina il runner con lavoro ancora in esecuzione. Le sue sessioni non ottengono nessun hook
post-session. Inoltre il runner non si deregistra, quindi il piano di controllo può impiegare alcuni minuti, e talvolta di più, per rimetterle in coda.
Pubblica un messaggio di stato prima di un riavvio
Prima di un riavvio che interromperà le sessioni, chiedi a un Owner di pubblicare un messaggio di stato sull'ambiente dalla pagina di amministrazione Cloud environments. Su claude.ai/code, i membri vedono il messaggio nelle loro sessioni in quell'ambiente. Chiedi all'Owner di impostare una scadenza al momento della pubblicazione, oppure di rimuovere il messaggio una volta che i runner sono di nuovo attivi.
Defer the drain past the first signal
Imposta --defer-shutdown-max-min <n> se desideri che un runner che stai riavviando continui a servire le sessioni che tiene per fino a n minuti, invece di drenarle al primo segnale. Al primo SIGTERM o SIGINT, il runner smette di accettare nuovo lavoro e continua a servire le sessioni che tiene. Continua a eseguire il polling in modo che il piano di controllo non rimetta in coda quelle sessioni. Richiede Claude Code v2.1.238 o successivo.
Cosa succede alle sessioni che il runner tiene dopo il primo segnale
Nei primi due stadi che seguono il segnale, il runner rilascia le sessioni, e una sessione rilasciata riprende su un runner nuovo quando il suo utente invia il messaggio successivo. Contando dal primo segnale, il runner attraversa tre stadi:
- Per i primi
nminuti: il runner serve le sue sessioni normalmente e continua ad applicare--startup-timeout-mine--kill-session-after-min. Se imposti anche--release-idle-session-min, il runner rilascia qualsiasi sessione il cui utente è stato inattivo per quel tempo, a meno che tu non trattenga quella sessione. Senza il flag, le sessioni inattive rimangono sul runner. - Quando gli
nminuti scadono: il runner rilascia ogni sessione che ancora tiene, inattiva o no. Il runner aspetta che il turno di una sessione a metà turno finisca, e fino a 60 secondi in più per le attività in background di un turno, prima di rilasciare quella sessione. - Quando la grazia post-release scade: il runner drena qualsiasi sessione che ancora tiene, e il piano di controllo rimette subito in coda ogni sessione drenata a un altro runner. La grazia post-release inizia quando gli
nminuti scadono ed è di 75 secondi con i valori predefiniti. Se imposti--drain-wait-secsopra i 60 secondi, la grazia post-release è invece--drain-wait-secpiù 15 secondi.
In qualsiasi stadio, il runner esce con 0 non appena non tiene più sessioni. Un secondo segnale interrompe gli stadi: il runner drena immediatamente, come fa al primo segnale senza --defer-shutdown-max-min. Una volta che un drenaggio è in corso, il segnale successivo forza l'uscita del runner. Questo vale sia che il drenaggio sia stato avviato da un secondo segnale sia dalla scadenza della grazia post-release.
Dimensiona il timeout di arresto
Dai al timeout di arresto del tuo host almeno la somma di tre parti: gli n minuti che configuri, la grazia post-release, e il drenaggio che Shutdown timing descrive. Con le impostazioni predefinite la grazia post-release è di 75 secondi e il drenaggio richiede fino a 80 secondi, quindi concedi n minuti più 155 secondi. Il runner stampa questa somma all'avvio ogni volta che --defer-shutdown-max-min è impostato.
Se il timeout di arresto scade prima che il runner finisca, le sessioni che ancora tiene non ottengono nessun hook post-session. Il runner non si deregistra, quindi il piano di controllo può impiegare alcuni minuti, e talvolta di più, per rimettere in coda quelle sessioni. Se non puoi dare al timeout di arresto quella somma, lascia --defer-shutdown-max-min non impostato in modo che il runner dreni invece al primo segnale.
Cosa raggiunge un hook post-session in esecuzione
L'hook post-session e il processo figlio della sessione Claude vengono eseguiti ciascuno nel proprio gruppo di processi POSIX, separato da quello del runner, quindi i meccanismi di arresto li raggiungono in modo diverso:
- Un
SIGTERMmentre il runner sta già drenando: forza immediatamente l'uscita del runner, saltando tutto ciò che rimane del drenaggio. Senza--defer-shutdown-max-min, questo è il secondoSIGTERMche il runner riceve. Nulla invia segnali a un hookpost-sessionin piena esecuzione, quindi su un host nudo dove un processo init adotta gli orfani, finisce da solo, ma senza supervisione: il suo budget di timeout non si applica più, e una scrittura sulla pipe di log chiusa può terminarlo conSIGPIPE, quindi un hook che deve sopravvivere a un'uscita forzata in quel contesto dovrebbe reindirizzare il proprio output a un file. Nelle ricette per container di questa pagina il runner è il PID 1 del container e la sua uscita termina il container, e con ilKillMode=control-grouppredefinito di systemd la terminazione a livello di cgroup raggiunge anche l'hook, come descrive la voce Cgroup-wide kills; in entrambi i casi, considera un'uscita forzata come fatale per l'hook e affidati invece al periodo di grazia. - Segnali a livello di gruppo di processi, come
kill -- -<pid>in uno script wrapper, il job control della shell, o un watchdog a livello di gruppo: raggiungono il runner e un sottoprocesso di un hookcheckoutin esecuzione, che rimane deliberatamente collegato al gruppo, ma non un hookpost-sessionin piena esecuzione né il processo figlio della sessione. - Cgroup-wide kills, come il
KillMode=control-grouppredefinito di systemd o ilSIGKILLche Kubernetes invia all'intero container quandoterminationGracePeriodSecondsscade: raggiungono tutto, incluso l'hook. L'isolamento del gruppo di processi non protegge da questi, motivo per cui il periodo di grazia deve coprire l'intero drenaggio. - Il timeout dell'hook stesso: quando un hook supera
--post-session-hook-timeout-sec, il runner inviaSIGTERMall'intero gruppo di processi dell'hook, quindiSIGKILLdue secondi dopo, in modo che un worker generato dall'hook con fork, come tar, rsync o git, termini insieme alla shell wrapper invece di sopravvivere come orfano. La supervisione del runner termina una volta che lo stdio dell'hook si chiude: un worker che ha reindirizzato il proprio output a un file e sopravvive allo stadioSIGTERMè fuori dalla portata del runner.
Quando il drenaggio inizia, e di nuovo in caso di uscita forzata, il runner registra nei log quanti hook post-session sono ancora in esecuzione, in modo che tu possa distinguere un drenaggio tranquillo da uno che è a metà di uno snapshot.
Mantieni la directory di base e la capacità identiche tra i runner
Se un runner muore a metà sessione, il server rimette in coda la sessione e un altro runner nell'ambiente la raccoglie. Quel runner deriva il percorso di checkout dal suo proprio --base-dir e --capacity: --capacity 1 controlla direttamente sotto --base-dir, e un --capacity sopra 1 usa worktree per sessione invece. Quando i runner nello stesso ambiente usano valori diversi per uno dei due flag, la directory di lavoro della sessione ripresa cambia, e i percorsi assoluti che l'agente ha registrato in precedenza, in modifiche, chiamate di strumenti, o le sue stesse note, puntano a una posizione che non esiste più.
Usa lo stesso --base-dir e --capacity su ogni runner in un ambiente, e non usare un valore per host come un ID istanza o nome host. Passa entrambi i flag esplicitamente, come fanno le ricette Kubernetes e Docker Compose, in modo che nessuno dei due valori dipenda da un valore predefinito.
La directory di base è predefinita a /workspace, con l'eccezione che la riga di riferimento --base-dir registra. Il runner ha bisogno di accesso in scrittura ad essa. All'avvio, prima di registrarsi, il runner crea la directory e conferma che può scrivere ad essa, e esce con cannot create or write to base directory quando non può. Un runner avviato come root crea il /workspace predefinito da solo. Per un runner non root, crea la directory e dai al runner la proprietà dell'utente prima di avviare il runner, o punta --base-dir a una directory che l'utente già possiede.
Riutilizza un checkout pre-riscaldato
Per i repository grandi, il clone può dominare l'avvio della sessione. Per saltare il clone freddo, fornisci tu stesso un clone al percorso in cui il runner mantiene il proprio. Senza un checkout hook, il runner mantiene un clone canonico per repository a <base-dir>/<repo-owner>/<repo> e lo riutilizza tra le sessioni:
- A
--capacity 1: il runner recupera il ref richiesto, staccaHEADe lo resetta duramente, il che è quasi istantaneo quando poco è cambiato. - A una
--capacitysuperiore a uno: il runner esegue il fetch in quel clone, quindi estrae da esso un worktree separato per ogni sessione. Un clone pre-riscaldato fa risparmiare il download ma non il checkout.
Fornisci il clone nell'immagine o su un volume persistente:
- Clone nell'immagine: costruisci il clone nella tua immagine runner a quel percorso. Ogni container fresco inizia quindi con il clone caldo senza riutilizzare un disco.
- Clone su un volume persistente: su runner che pre-blocchi a un account di un utente con
--lock-to-account, punta--base-dira un volume persistente, in modo che il disco serva solo quell'account. Un runner pre-bloccato non raccoglie mai sessioni di canale Claude Tag, quindi questa opzione non si applica ai runner che le servono.
Cosa il percorso di riutilizzo fa e non garantisce:
-
Profondità del clone: crea il clone senza
--depth, con o senza--single-branch. Il runner non passa mai--depthquando esegue il fetch in un clone con un remoteorigin, anche se impostiCLAUDE_RUNNER_FETCH_DEPTH. Con un clone completo, ogni sessione scarica solo i commit che mancano al clone. Con un clone shallow, una sessione su un branch che si è separato prima del commit più vecchio del clone scarica l'intera cronologia di quel branch. -
Le modifiche tracciate si resettano, i file non tracciati persistono: a
--capacity 1, ogni sessione inizia da un reset duro che cancella le modifiche tracciate della sessione precedente, ma il runner non esegue maigit clean, quindi i file non tracciati dalle sessioni precedenti dell'owner bloccato rimangono nell'albero. -
Directory per sessione persistono anche: accanto al checkout, il runner crea voci per sessione sotto
<base-dir>/_sessions/per ogni sessione che esegue. La directory di configurazione Claude della sessione contiene una copia locale della trascrizione della conversazione. Accanto ad essa si trovano i file caricati della sessione, quando la sessione ne ha. La directory della sessione si trova lì anche: contiene qualsiasi worktree per sessione e checkout di hookcheckoutmentre la sessione è in esecuzione, e mantiene qualsiasi altra cosa Claude abbia scritto in essa.Per impostazione predefinita il runner lascia questi in posizione quando la sessione termina, quindi su un disco che sopravvive al processo runner si accumulano. Ogni sessione viene eseguita come l'utente del runner stesso, quindi qualsiasi sessione successiva che il disco serve può leggerli. Se mantieni un
--base-dirpersistente, dimensiona il volume per quella crescita. Lo stesso si applica a qualsiasi configurazione che riavvia il runner sullo stesso filesystem, inclusa la ricetta Docker Compose. -
Con
--remove-session-state, le directory per sessione non persistono: avvia il runner con--remove-session-stateper fargli eliminare le directory per sessione di ogni sessione mentre la sessione termina. L'eliminazione è best-effort: le directory rimangono quando il runner viene ucciso prima che la sua pulizia venga eseguita. Il clone canonico e i file che una sessione ha scritto altrove sull'host, come la directory temporanea, rimangono comunque. -
Con il proxy git, il reset diventa un checkout: con
--use-anthropic-git-proxy, il runner sanitizza il.git/del clone prima di ogni sessione. Mantiene l'object store e lo stato shallow, ed elimina l'indice. Senza l'indice, ogni sessione inizia con un checkout completo dell'albero di lavoro invece di un reset quasi istantaneo. Il runner esegue comunque il fetch nel clone esistente invece di clonare di nuovo il repository. I pre-warm dei submodule non sono supportati sotto il proxy. -
Con il proxy git, un pre-warm
reftableperde i suoi ref: quando il runner sanitizza.git/prima di ogni sessione, mantiene.git/refs/e rimuove la directory.git/reftable/. Un clone pre-riscaldato nel formato dei refreftabledi git perde così i suoi ref, quindi pre-riscalda invece il clone nel formato dei reffilesdi git. -
I clone lunghi non hanno bisogno di workaround: il runner limita ogni operazione git con un watchdog senza progresso di 120 secondi e un hard cap di 30 minuti, non un timeout piatto, quindi un clone freddo lento che continua a segnalare progresso si completa.
Conferma che il runner usa il tuo clone
Per confermare che il runner usa il tuo clone, avvialo con --log-level debug e avvia una sessione sul repository. Il runner registra Prefetched repo found at e il percorso del clone. Se invece registra No prefetched repo, fresh init at, non ha trovato alcun clone utilizzabile al percorso indicato in quella riga. Verifica che il tuo clone si trovi in quel percorso, sia di proprietà dell'utente del runner e abbia un remote chiamato origin.
Se il runner trova il tuo clone e le sessioni si avviano comunque lentamente, verifica se il clone è shallow.
Fissa la versione
Per impostazione predefinita, il processo figlio di ogni sessione esegue il binario del runner stesso, e il runner disattiva l'auto-aggiornamento all'interno delle sessioni che genera. Con questa impostazione predefinita, ogni sessione esegue la versione che hai installato sull'host o incluso nell'immagine. Un aggiornamento a livello di host ha effetto la prossima volta che il runner si avvia.
Scegli quale versione eseguono le tue sessioni e quando cambia:
- Prima di fissare una versione: controlla le versioni di Claude Code che i modelli richiedono per ogni modello che le tue sessioni utilizzano. Se un modello richiede una versione più recente di quella eseguita dalle tue sessioni, il server rifiuta le richieste per quel modello con Claude Code does not support this model.
- Per mantenere una flotta su una versione: costruisci l'immagine con una versione fissata, o su un host nudo installa una versione specifica e disabilita gli auto-aggiornamenti
- Per aggiornare una flotta fissa: leggi le voci del changelog tra la tua versione e quella che stai installando, quindi installa la versione più recente o ricompila l'immagine e riavvia i runner
- Per aggiornare i runner on-demand: leggi le voci del changelog tra la tua versione e quella che stai installando, quindi cambia l'immagine che il tuo hook
spawn-runneravvia. Ogni nuovo runner ottiene la nuova versione. Un runner già attivo, incluso un runner in standby avviato da--min-idle, mantiene la sua versione finché non termina. Non riavviarlo, perché il suo ordine di lavoro è monouso. - Se le sessioni avviano un binario diverso: se usi
--exec-patho un hookcommandper avviare un binario di Claude Code diverso da quello del runner, le sessioni eseguono la versione di quel binario. Aggiorna quel binario quando aggiorni il runner. - Plugin: i marketplace dei plugin non si auto-aggiornano neanche; imposta
FORCE_AUTOUPDATE_PLUGINS=1nell'ambiente del runner per lasciare che i plugin si auto-aggiornino mentre il binario rimane fissato
Scala la flotta
Il tuo orchestrator decide quando aggiungere o rimuovere runner. A causa del blocco one-owner-per-runner, il numero minimo di repliche è il numero di utenti e agenti Claude Tag che ti aspetti siano attivi contemporaneamente; --capacity controlla il parallelismo all'interno delle sessioni di un owner, non tra owner.
Due approcci di scaling sono disponibili:
- Flotta fissa: esegui un set statico di repliche runner e scala sulle metriche Prometheus che ogni runner serve
- Runner on-demand: esegui il sottocomando
claude self-hosted-runner orchestrator, che esegue il polling di Anthropic per le sessioni in coda senza runner disponibile e invoca il tuo hookspawn-runnerper avviarne uno per sessione. Vedi Runner on-demand.
Rilascia le sessioni inattive su una flotta fissa
Se le sessioni restano in coda su una flotta fissa mentre tutti i runner sono online, la causa potrebbe essere costituita dalle sessioni inattive. Per impostazione predefinita, una sessione mantiene il suo slot dopo la fine del suo turno, e il suo runner resta bloccato sull'owner di quella sessione. Le sessioni di altri owner non possono usare gli slot liberi di quel runner. Se l'attività pianificata cloud del tuo team avvia una nuova sessione a ogni esecuzione, le sue sessioni inattive possono occupare tutti gli slot.
Per verificarlo, leggi claude_code_self_hosted_runner_session_idle_seconds nelle metriche Prometheus di ogni runner. Ogni sessione detenuta da un runner ha la propria serie: 0 mentre la sessione è occupata, altrimenti i secondi trascorsi da quando è diventata inattiva. Scegli il tempo di inattività che vuoi consentire: più lungo delle pause che le persone fanno a metà di un'attività, e abbastanza breve da rilasciare ogni esecuzione prima che inizi quella successiva dell'attività pianificata. Confronta le serie con questo valore.
- Serie sopra quel tempo, ad esempio
1800per 30 minuti: le sessioni inattive occupano quegli slot, quindi imposta--release-idle-session-min - Tutte le serie sotto quel tempo: nessuna sessione è rimasta inattiva così a lungo. Aggiungi runner, oppure imposta
--kill-session-after-minper le sessioni che non diventano mai inattive. - Un runner senza serie: nessuna sessione lo occupa. Consulta Sessions stay queued in Risoluzione dei problemi.
- Un runner le cui metriche non rispondono: il suo processo potrebbe essere terminato. Consulta An environment that looks healthy in Quando il runner termina.
Per rilasciare le sessioni inattive, avvia ogni runner con --release-idle-session-min e quel tempo in minuti, ad esempio --release-idle-session-min 30. Il runner rilascia quindi qualsiasi sessione che resta inattiva così a lungo, sia che l'abbia avviata un'attività pianificata sia una persona, a meno che tu non la trattenga per i processi che ha lasciato in esecuzione.
- Lavoro non inviato: una sessione rilasciata riprende al messaggio successivo, senza il lavoro di cui non aveva fatto il push. Per conservarne i commit, consulta Le sessioni riprese perdono il lavoro non inviato.
- Uscita del runner: un runner che rilascia la sua ultima sessione avvia una normale uscita. Fai in modo che il tuo supervisor lo riavvii su un filesystem pulito, come descritto in Rafforza il tuo deploy.
- Sessioni che non diventano mai inattive: imposta anche
--kill-session-after-min, perché alcune sessioni non vengono considerate inattive
Mantieni una sessione inattiva mentre i suoi processi sono in esecuzione
Per mantenere una sessione inattiva finché non terminano i processi che ha lasciato sul tuo host, come i job nohup, avvia il runner con --hold-idle-file e continua a eseguire il touch di quel file dall'host. Richiede Claude Code v2.1.311 o versioni successive.
Questi passaggi presuppongono un runner non root su un host. Se il tuo runner viene eseguito come root, le sue sessioni possono eseguire il touch del file da sole, quindi eseguilo prima come utente dedicato.
1. Scegli un percorso per il file
Scegli un file regolare locale, ad esempio /var/lib/claude-hold/hold-idle, in una directory in cui le sessioni non possono scrivere, directory padre incluse, così che una sessione non possa scriverci. Il runner e le sue sessioni condividono un unico utente del sistema operativo, quindi crea prima quella directory, di proprietà di un altro utente come root, con modalità 755.
2. Avvia il runner
Dalla tua shell, avvia il runner con --hold-idle-file, --release-idle-session-min e --max-hold-min, il limite massimo di un trattenimento, calcolato da quando una sessione diventa inattiva. Senza questa opzione, il limite è di un giorno. Impostalo su un valore superiore a --release-idle-session-min, altrimenti nulla viene trattenuto. Sostituisci <writable-dir> come descritto nella guida rapida. Questo esempio rilascia le sessioni inattive dopo 30 minuti e rilascia una sessione trattenuta al massimo 4 ore dopo che è diventata inattiva:
claude self-hosted-runner \
--environment-secret-file /etc/claude/environment-secret \
--base-dir '<writable-dir>' \
--release-idle-session-min 30 \
--hold-idle-file /var/lib/claude-hold/hold-idle \
--max-hold-min 240
3. Esegui il touch del file mentre i processi sono in esecuzione
Mentre i processi sono in esecuzione, esegui touch sul file dall'host come proprietario della directory, più spesso di ogni tre minuti. Il primo touch crea il file. Ad esempio, aggiungi questa riga al crontab di root:
* * * * * pgrep -x nightly-build > /dev/null && touch /var/lib/claude-hold/hold-idle
La riga viene eseguita una volta al minuto ed esegue il touch del file mentre è in esecuzione un qualsiasi processo chiamato nightly-build, così una sessione che ne avvia uno resta trattenuta fino al limite. Sostituisci il controllo pgrep con il metodo con cui il tuo host individua ciò che le sessioni hanno lasciato in esecuzione.
Il controllo deve smettere di trovare corrispondenze quando questi terminano, altrimenti ogni sessione inattiva su quel runner resta trattenuta fino al limite.
4. Conferma il trattenimento
Mentre i processi sono in esecuzione, verifica che l'ora di modifica del file resti inferiore a tre minuti fa. Quando il tempo di inattività di una sessione si esaurisce, cerca idle release held nel log del runner. Se non compare alcuna riga di questo tipo, leggi leftover-hold= nella riga watchdog config che il runner registra all'avvio, che mostra il file e il limite, oppure il motivo per cui nulla viene trattenuto.
Tre minuti dopo l'ultimo touch, il trattenimento termina. Entro circa un altro minuto, il log mostra idle release no longer held e il runner rilascia la sessione.
Anche mentre il file continua a cambiare, un trattenimento può terminare, in casi come questi:
- Oltre il limite:
--max-hold-minsi esaurisce. Per trattenere più a lungo, aumentalo - Rilasci forzati: hai impostato
--retire-at,--defer-shutdown-max-mino--kill-session-after-min, e quel limite viene raggiunto. Per trattenere più a lungo, imposta un limite successivo
Problemi noti e limitazioni
Le seguenti sono le limitazioni in questa versione, con workaround dove uno esiste.
Il traffico del connettore lascia la tua rete
Anthropic chiama gli strumenti del connettore dalla sua stessa infrastruttura piuttosto che dal tuo runner. Gli strumenti del connettore sono i connettori claude.ai, come GitHub, Slack, e Linear. Quando Claude usa un connettore in una sessione self-hosted, quel traffico va attraverso api.anthropic.com piuttosto che originare all'interno del tuo confine di rete.
Per mantenere un connettore fuori dalle sessioni self-hosted, filtralo con le impostazioni di politica allowedMcpServers e deniedMcpServers. Claude Code applica queste impostazioni ai connettori che Anthropic consegna così come ai server che semini dall'host runner e ai server che gli utenti aggiungono, quindi se distribuisci una lista di indirizzi consentiti per altri server, Claude Code blocca anche i connettori consegnati. Per mantenere i connettori disponibili insieme a una lista di indirizzi consentiti basata su URL, aggiungi voci che corrispondono ai percorsi proxy di Anthropic per i connettori consegnati:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
Se il traffico dello strumento deve rimanere all'interno della tua rete, esegui gli strumenti equivalenti come server MCP locali sull'immagine del runner invece. Vedi Server MCP.
Alcune sessioni non contano come inattive
Una sessione che tiene un compito di background che non finisce mai non conta come inattiva, quindi --release-idle-session-min non rilascerà lo slot di quella sessione. Una sessione che sta aspettando un'approvazione richiesta dall'interno di una chiamata di strumento in esecuzione non conta neanche come inattiva. Imposta sempre --kill-session-after-min insieme ad essa come un hard backstop in modo che nessuna sessione possa tenere uno slot indefinitamente.
--kill-session-after-min è un backstop per le sessioni runaway. Su un runner su v2.1.260 o successivo, una sessione che raggiunge il limite non viene terminata immediatamente. Il runner le dà una finestra di grazia, 15 minuti per impostazione predefinita, che puoi cambiare con SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS:
- Se la sessione sta aspettando il suo utente, il runner la rilascia. Se il suo turno è finito e tiene solo compiti di background, il runner aspetta fino a 60 secondi affinché questi compiti finiscano e poi la rilascia. La sessione riprende quando il suo utente invia il suo prossimo messaggio.
- Se un turno è ancora in esecuzione, il runner aspetta che il turno finisca, o che la sessione aspetti successivamente il suo utente, e poi la rilascia.
- Se la sessione è ancora sul runner quando la finestra di grazia scade, il runner la termina, e il lavoro di qualsiasi turno in esecuzione è perso. Un turno che aspetta un'approvazione richiesta dall'interno di una chiamata di strumento in esecuzione è un modo in cui una sessione sopravvive alla finestra.
Una sessione rilasciata riprende da un clone fresco, quindi il lavoro che non aveva spinto è comunque perso; vedi Le sessioni riprese perdono il lavoro non spinto. Prima di v2.1.260, il runner terminava ogni sessione al limite, dopo aver aspettato al massimo la finestra di grazia affinché un turno in esecuzione finisca.
Imposta il flag sopra la tua sessione più lunga prevista, come --kill-session-after-min 480 per 8 ore. Per liberare slot dalle conversazioni che diventano inattive, usa --release-idle-session-min invece.
Le sessioni riprese perdono il lavoro non spinto
Una sessione che riprende su un nuovo runner perde il lavoro che non aveva spinto, perché quel runner clona di nuovo il repository dal suo branch iniziale.
Per conservare il lavoro di cui è stato fatto il commit, imposta --push-outcome-on-release su ogni runner nell'ambiente. Un runner con il flag esegue un push best-effort dei branch di risultato della sessione prima del rilascio, e la sessione ripresa parte da quei commit. Un runner senza il flag riprende la sessione dal suo branch iniziale. Il push usa le credenziali git proprie dell'host del runner, anche su un runner che usa git gestito da Anthropic.
[!WARNING]
Prima di impostare il flag, limita chi può eseguire il push sui ref
claude/*sul remote di origine. Alla ripresa, il runner recupera il branch precedentemente spinto senza verificare chi ne ha eseguito il push.
Anche con il flag impostato, salva questo lavoro in un altro modo:
- Modifiche senza commit: vengono comunque perse. Vedi Chiedere alle sessioni di eseguire il push del proprio lavoro.
- Repository estratti tramite un hook del ciclo di vita
checkout: non vengono sottoposti a push. Crea uno snapshot di questi repository dall'hookpost-session. - Commit in una sessione che qualcuno archivia o elimina, o il cui runner viene rilasciato dal menu della sessione: non fare affidamento sul push del flag. Per salvarli, esegui il push di
HEADsu un branch di recupero da un hookpost-session. L'esempio presente lì esegue il push solo dopo un commit di snapshot, quindi fai in modo che il tuo esegua il push anche quando non ci sono modifiche senza commit.
Limitazioni aggiuntive
- Un comando interrotto dalla perdita di un runner può essere eseguito di nuovo: quando la sessione riprende su un altro runner, Claude vede la chiamata allo strumento interrotta con un risultato che indica che il suo esito è sconosciuto.
- Un repository aggiunto a metà sessione può non riuscire a essere clonato: Claude lo clona con
git clonetramite HTTPS. Su un runner senza--use-anthropic-git-proxy, il clone fallisce con un errore di autenticazione git se nulla sull'host può leggere il repository. Quando possibile, seleziona ogni repository di cui la sessione ha bisogno quando la crei.- Quando la sessione riprende: un hook
checkoutviene eseguito anche per il repository aggiunto, conCLAUDE_RUNNER_REPO_REFvuoto, quindi fai in modo che il tuo hook gestisca questo caso.
- Quando la sessione riprende: un hook
- Alcuni connettori non appaiono nelle sessioni self-hosted: un connettore che non hai ancora connesso nelle Impostazioni di claude.ai non è elencato in una sessione self-hosted, e la sessione non ti chiederà di connettarlo. Connettilo prima nelle Impostazioni, quindi avvia una sessione fresca. L'aggiunta di un connettore a una sessione già in esecuzione non rende i suoi strumenti disponibili a Claude; avvia una sessione fresca per raccogliere un connettore appena aggiunto.
Segnala un problema
Per i problemi con gli ambienti self-hosted, contatta il tuo team di account Anthropic.
Troubleshooting
Per una diagnosi guidata, eseguire il subcommand doctor sull'host del runner. Il subcommand doctor avvia una sessione Claude Code interattiva con i log e lo stato del runner allegati. Accedere con claude auth login su quell'host prima in modo che la sessione possa interrogare l'ambiente, i suoi runner e le sue sessioni in coda. Senza questo accesso, ad esempio quando l'host si autentica con una chiave API, è limitato all'endpoint di salute locale, alle metriche e al log del runner, e legge il log solo se è stato avviato il runner con --log-file.
claude self-hosted-runner doctor
Problemi comuni:
-
Il runner non appare nell'ambiente: verifica che l'host possa raggiungere
api.anthropic.comtramite HTTPS e che il segreto dell'ambiente sia attuale. In caso di errore di autenticazione, il runner registra[runner:fatal]con il motivo del rifiuto. -
Il runner esce all'avvio con
cannot create or write to base directory: il runner non può creare o scrivere in--base-dir, che per impostazione predefinita è/workspace. Correggere la proprietà della directory o puntare--base-dira un percorso scrivibile, come descritto in Keep the base directory and capacity identical across runners. Se il runner registra invece[runner:fatal]dicendo che il controllo della directory di base è scaduto, la directory si trova su un mount NFS o CSI bloccato. Controllare l'integrità del mount piuttosto che i permessi. Il runner stampa entrambi questi errori di avvio su stderr prima di aprire--log-file, quindi cercarli nel terminale o nei log del container della piattaforma piuttosto che nel file di log. Prima della v2.1.225, il runner non controllava la directory di base all'avvio e questa configurazione errata causava il fallimento delle sessioni dopo il pickup. -
Il runner esce all'avvio con
RegisterRunner failed:e un errore di certificato TLS: l'errore può essere, ad esempio,self signed certificate in certificate chain. Il runner non considera attendibile l'autorità di certificazione (CA) dietro il certificato che ha ricevuto, come accade dietro un proxy che ispeziona il traffico TLS. ImpostaNODE_EXTRA_CA_CERTSnell'ambiente del runner su un file PEM con la CA radice del proxy.Non devi impostarla di nuovo per le sessioni, che la ereditano. Git non la legge: per i clone attraverso quel proxy, imposta anche
GIT_SSL_CAINFO. -
Il tuo script di avvio stampa un errore di scrittura contenente
Resource temporarily unavailableed esce: il tuo script condivide il proprio stdout o stderr con il processoclaude self-hosted-runnero orchestrator che avvia, il quale ha impostato quella pipe o quel socket condiviso in modalità non bloccante. Quando la pipe è piena, una scrittura dal tuo script fallisce con questo errore del sistema operativo invece di attendere, e conset -elo script esce.Per eliminare l'errore, invia l'output del runner o dell'orchestrator a un file che l'utente del tuo script può creare. Aggiungi
>> "$HOME/claude-runner.log" 2>&1al comando che lo avvia, prima di qualsiasi&che lo mette in background, e sostituisci<writable-dir>come descritto nella guida rapida:claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --base-dir '<writable-dir>' >> "$HOME/claude-runner.log" 2>&1L'output del tuo script continua ad andare dove andava prima, mentre ciò che il runner scrive su stdout e stderr, inclusi i suoi errori di avvio, va in quel file. Usa questo reindirizzamento della shell anziché il solo
--log-file, che replica il log in un file mentre il runner continua a scrivere su stdout e stderr. Se passi già--log-file, mantienilo comunque, perché il reindirizzamento non cambia ciò che quel file riceve. -
L'orchestrator registra
poll failed:con un errore di certificato TLS e continua a riprovare: l'orchestrator non considera attendibile la CA dietro il certificato che ha ricevuto, come accade dietro un proxy che ispeziona il traffico TLS. ImpostaNODE_EXTRA_CA_CERTSnell'ambiente dell'orchestrator su un file PEM con la CA radice del proxy. Impostala anche per i runner avviati dal tuo hookspawn-runner.Ad esempio, una riga può terminare con
poll failed: self signed certificate in certificate chain — retrying in 1s, con le parole della libreria TLS tra i due punti e il trattino. Per verificare la correzione, controlla checonnectedsiatruenel corpo di/healthzdell'orchestrator. -
Le sessioni rimangono in coda: ogni runner online può essere bloccato a un proprietario diverso. Controllare la metrica
claude_code_self_hosted_runner_locked_accountdi ogni runner o il campolocked_accountdella sua riga di log[runner:health]per vedere chi la detiene. Entrambi mostrano l'email del proprietario solo dopo che il runner ha ricevuto un token di sessione con un claimact.email, che le sessioni di un agente Claude Tag non hanno mai. Senza il claim, il runner non emette alcuna serielocked_accounte registralocked_account=yes, il che indica che il runner è bloccato ma non a quale proprietario. Aggiungere repliche o attendere che un runner esistente si svuoti e si riavvii. Se l'ambiente utilizza runner on-demand, controllare l'orchestrator; vedere On-demand runners. -
Le sessioni falliscono subito dopo la presa in carico: apri la sessione in claude.ai/code per vedere l'errore. Se l'errore mostra che git non è riuscito ad autenticarsi, controlla le credenziali git del runner. Su un runner avviato con
--use-anthropic-git-proxy, consulta Quando le sessioni non si avviano su un runner con il git proxy.Una directory di base non scrivibile arresta il runner all'avvio invece di far fallire le sessioni. Consulta la voce Il runner esce all'avvio con
cannot create or write to base directoryin questo elenco. -
Una sessione viene presa in carico, ma Claude non risponde mai al suo primo messaggio: se i tuoi runner raggiungono
api.anthropic.comattraverso un proxy, configuralo in modo che lasci passare la risposta/worker/events/streamdella sessione man mano che arriva. Aumentare i timeout del runner non serve. Per confermare la causa nel log del runner, consulta Una sessione viene presa in carico, ma Claude non risponde mai al primo messaggio. -
Le sessioni non si avviano su un runner che ha impostato
--use-anthropic-git-proxy: cerca nel log del runneraccess denied by the git proxy, oppure un errore git che nomina un indirizzoapi.anthropic.comcontenente/git_proxy/. Per capire se Anthropic ha servito la sessione e correggere la causa, consulta Quando le sessioni non si avviano su un runner con il git proxy. -
Le sessioni non riescono a raggiungere la rete attraverso un proxy di uscita autenticante: quando l'origine impostata con
--proxy-authorization-commando--proxy-authorization-filefallisce, scade dopo 30 secondi o produce un valore vuoto, il runner risponde a quella connessione con502 Bad Gatewaye registra il motivo. Il runner redige lo stderr del comando in quel log e non registra mai il valore dell'intestazione. Con--proxy-authorization-command, eseguire il comando stesso sull'host per confermare che stampa l'intero valore dell'intestazione su stdout. Se il runner esce invece all'avvio concould not start the proxy-authorization listener, non ha potuto aprire il suo listener di loopback. -
Il runner registra righe
Poll failedcontenentirejecting the malformed poll response: il runner ha ricevuto una risposta di work-poll il cui corpo non è il JSON previsto dalla coda. Una possibile causa è un proxy di intercettazione o un captive portal che risponde con una propria pagina. Se il runner si connette attraverso un proxy, configura il proxy in modo che lasci passare inalterate le risposte daapi.anthropic.com.Il runner rifiuta la risposta malformata, la conteggia sotto il tipo
transportdella metricaclaude_code_self_hosted_runner_poll_errors_totale riprova secondo la pianificazione per i poll falliti descritta in Ciclo di vita della sessione. Il runner continua a servire le sue sessioni attive. Prima della v2.1.246, il runner interpretava una risposta di questo tipo come una coda di lavoro vuota, il che poteva terminare le sue sessioni attive o farlo uscire. -
Una sessione fallisce con
no longer exists on the remoteocouldn't find remote ref: il runner interpreta il messaggiocouldn't find remote refdi git come un branch eliminato. Il branch potrebbe comunque esistere. Cercalo sul tuo host git, esattamente con il nome riportato nel messaggio, poi verifica ciascuna causa applicabile:- Il branch non esiste più: ripristinalo, come chiede il messaggio.
- Il branch esiste e non amministri il runner: invia all'amministratore del runner il messaggio completo e comunicagli che il branch esiste.
- Il branch esiste e il runner ha un hook
checkout: un comando git nel tuo hook può stamparecouldn't find remote refsu stderr prima che l'hook esca con un codice diverso da zero. Trova quel comando e correggi il ref che richiede o il remote da cui esegue il fetch. Il runner passa al tuo hook la revisione richiesta dalla sessione comeCLAUDE_RUNNER_REPO_REF. - Il branch esiste e il runner non ha un hook
checkout: il runner esegue il fetch del branch da solo. Se al repository si applica un--git-host-rewrite, verifica che l'host verso cui riscrive abbia il branch con lo stesso nome, e in caso contrario esegui il push del branch su quell'host. - Il branch esiste e il runner è precedente alla v2.1.301: aggiorna il runner come descritto in Fissa la versione, in modo che un errore diverso mostri il proprio messaggio. Prima della v2.1.301, anche altri errori con
couldn't find remote refnel loro output venivano segnalati come branch eliminato.
Una volta aggiornato il runner o corretta la causa, riprova la sessione o avviane una nuova. Se nessuno di questi controlli individua la causa, segnala un problema.
Per l'origine a cui la sessione invia i risultati, un branch eliminato fa fallire la sessione. Per un'origine git che la sessione legge soltanto, il runner salta quell'origine e continua con le rimanenti. Il runner fa fallire la sessione quando saltare l'origine la lascerebbe senza alcun repository. Prima della v2.1.228, una sessione di questo tipo si avviava in una directory vuota.
-
Claude Tag a volte non pubblica la sua risposta in Slack: il promemoria di risposta è un hook fornito dal control plane. Verifica con
claude --versionche il runner sia Claude Code v2.1.229 o successivo, poiché i runner meno recenti ignorano gli hook forniti; verifica chepython3 --versionriesca all'interno dell'immagine del runner, poiché gli hook eseguonopython3; e verifica che nessun file di impostazioni disattivi gli hook, che si tratti didisableAllHooksinmanaged-settings.json, nel~/.claude/settings.jsondell'utente del runner o nel.claude/settings.jsono.claude/settings.local.jsondel repository, oppure diallowManagedHooksOnlyo di unostrictPluginOnlyCustomizationche copre gli hook nelle impostazioni gestite. -
Una sessione inizia senza uno dei suoi repository: su un runner senza un hook
checkout, l'host git può rifiutare il controllo di accesso del runner per un repository che la sessione legge solo. Il runner quindi salta quel repository, registra una riga[runner:warn] could not access context sourceche nomina il rifiuto, e avvia la sessione su quelli rimanenti.Con le proprie credenziali git, il runner può saltare un repository quando l'host risponde che il repository non è stato trovato, git non trova credenziali per l'host o l'autenticazione fallisce.
Con
--use-anthropic-git-proxy, quando il git proxy rifiuta un repository conaccess denied by the git proxy, il runner lo salta. La riga di log indica quindiblocked by the git proxy. Per le cause da verificare, consulta il casoremote: access denied by the git proxyin Quando le sessioni non si avviano su un runner con il git proxy.Un errore di rete, un timeout o qualsiasi altro HTTP
403fa comunque fallire l'avvio della sessione, così come un rifiuto per un repository a cui la sessione invia i risultati. Il runner fa comunque fallire una sessione che, saltando il repository, resterebbe senza alcun repository.Il controllo di accesso viene eseguito di nuovo ogni volta che la sessione inizia su un runner, quindi una volta che l'identità git del runner ha accesso in lettura, il prossimo avvio clona il repository. Prima della v2.1.274, ognuno di questi rifiuti faceva fallire l'avvio della sessione.
-
Il log mostra
could not read context source: questa riga, a differenza dicould not access context source, significa che l'host git ha continuato a rispondere con HTTP429, oppure che un wrapper installato comegitha continuato a uscire con75. Il runner ha saltato quel repository. Per riprovare, avvia una nuova sessione. Il runner riprova inoltre il repository ogni volta che questa sessione si avvia su un runner.Senza hook
checkout, il runner può saltare un repository che la sessione legge soltanto, a meno che non sia il primo tra i repository della sessione. Prima della v2.1.311, l'avvio della sessione falliva. -
Una sessione non si avvia e mostra
This runner couldn't read: il runner ha chiesto un repository all'host git con le sue credenziali git e ha ricevuto una risposta di repository non trovato. Il repository non esiste, oppure quelle credenziali non possono raggiungerlo.- Se amministri il runner: concedi alle sue credenziali git l'accesso in lettura al repository, poi avvia una nuova sessione.
- Se hai avviato tu la sessione: se il repository non esiste, avvia una nuova sessione con il repository corretto. Se il runner non riesce a raggiungere il repository, chiedi all'amministratore del runner di concedere l'accesso, poi avvia una nuova sessione.
-
Le sessioni impiegano minuti per avviarsi o riprendere: una possibile causa è un clone da zero. In una flotta che avvia ogni runner su un disco vuoto, senza hook
checkout, il runner clona da zero i repository della sessione prima di avviare Claude Code. Una sessione che riprende su un disco vuoto attende di nuovo lo stesso clone.Per evitare il clone da zero, fornisci un checkout pre-riscaldato. Se ne fornisci già uno, verifica che il runner lo usi. Dove è il runner stesso a creare il clone ed esce dopo una sola sessione, abbassa
CLAUDE_RUNNER_FETCH_DEPTHdal valore predefinito di 50 commit per recuperare meno cronologia.Se usi un hook, questo viene eseguito al posto del clone del runner, quindi esamina invece ciò che fa il tuo hook. Osserva la metrica
claude_code_self_hosted_runner_session_init_duration_secondsper vedere quanto durano gli avvii. -
I turni falliscono con un 401: quando un turno termina con un 401 o un 403 dall'API di Anthropic, il runner recupera da Anthropic un nuovo
CLAUDE_CODE_OAUTH_TOKENe lo passa alla sessione. Il turno fallito non viene ritentato. Questo token ha una durata breve e il runner lo ruota tramite lo stdin della sessione.Quando un recupero fallisce, il runner registra una riga
inference_token refresh failedche dice quando riproverà, e continua a riprovare finché la sessione è in esecuzione.Se ogni chiamata inizia a fallire circa 30 minuti in una sessione, uno script wrapper ha probabilmente reciso lo stdin della sessione, quindi le rotazioni dei token non possono raggiungerlo; vedere Keep stdin and file descriptor 3 attached.
Prima della v2.1.274, il runner smetteva di riprovare un recupero fallito dopo alcuni tentativi e attendeva il prossimo programmato. Un turno fallito non ha attivato un recupero, quindi ogni turno falliva con un 401 fino al prossimo recupero programmato.
-
Un turno fallisce in claude.ai/code con
This model needs a newer version of Claude Code: la versione di Claude Code con cui i tuoi runner avviano le sessioni è più vecchia di quella richiesta dal modello della sessione, quindi l'API rifiuta la richiesta. Aggiorna i runner alla versione indicata dal messaggio, o a una successiva, come descritto in Fissa la versione. Nel frattempo, gli utenti possono scegliere un modello diverso nella sessione. -
Il pod viene terminato durante lo scarico: aumentare
terminationGracePeriodSecondsad almeno il valore che il runner registra all'avvio. Vedere Shutdown timing. -
Il runner registra una riga
[runner:log]che termina conwas paused or too slow: il runner ha continuato a funzionare e non ha bisogno di essere riavviato. Finché il flusso stdout o stderr indicato nella riga non è stato letto di nuovo, il runner ha scartato tutte le righe di quel flusso tranne gli ultimi 256 KiB. Se hai passato--log-file, cerca lì le righe scartate. -
Il runner registra
Nobody can read this process's stdout anymore: ciò che leggeva lo stdout del runner non c'è più, quindi il runner smette di scrivere lì per il resto della sua esecuzione e continua a funzionare. Finché stderr è ancora aperto, questa riga e le successive righe di stato del runner, incluse le righe[runner:fatal], vanno su stderr, quindi leggile lì.
Una volta inizializzato il logging, il runner scrive il suo log del ciclo di vita, incluse le righe [runner:fatal], su stdout e l'output di debug su stderr, il tutto come righe di testo semplice anziché JSON. Gli errori relativi alla directory di base descritti nelle voci di risoluzione dei problemi qui sopra vengono stampati su stderr prima di quel momento. Acquisisci entrambi i flussi con --log-file, che consente anche a self-hosted-runner doctor di seguirli, oppure con la raccolta dei log della tua piattaforma.
Il processo figlio di ogni sessione scrive un log di debug separato. In caso di errore il runner visualizza il log della coda insieme alla sessione in claude.ai/code. A meno che non sia stato avviato il runner con --remove-session-state, mantiene anche il log di una sessione fallita su disco e stampa il suo percorso nel log del runner.
When the runner exits
Non riavviare un runner on-demand, perché il suo ordine di lavoro è monouso. Un runner che esce subito dopo l'avvio necessita di una gestione diversa da uno che esce per qualsiasi altro motivo.
- Un'uscita normale: il runner ha terminato le sue sessioni e si è scaricato, ha raggiunto il suo tempo di ritiro, o gli è stato detto di fermarsi. Riavviarlo in modo che l'ambiente abbia di nuovo capacità. Runner lifecycle descrive queste uscite.
- Un avvio fallito: il runner non può avviarsi con la configurazione o l'host che gli è stato assegnato, quindi esce secondi dopo l'avvio, e esce nello stesso modo ogni volta che lo riavvii. Riavviarlo più velocemente non aiuta. Qualcuno deve leggere il suo output e correggere la causa.
- Contatto perso: un runner che non riesce a raggiungere Anthropic per un tempo superiore al suo lease, ad esempio mentre il suo host è in sospensione, può essere rimosso dall'ambiente. Quando un runner rimosso si riconnette, esce. Il suo log può mostrare una riga
[runner:fatal]che contienerunner record gone server-sideoppure, dopo un'interruzione più lunga,poll auth failed. Il runner non si registra di nuovo da solo, quindi riavvialo.
Configurare il supervisore per riavviare il runner ogni volta che esce, per attendere più a lungo tra i riavvii quando il runner continua a uscire subito dopo l'avvio, e per avvisare qualcuno quando ciò continua a accadere.
In produzione, fai in modo che il tuo supervisore riavvii il runner su un disco pulito, in un nuovo container o in una nuova VM, come raccomandato in Rafforza il tuo deploy.
Recognize a failed start
Quando il runner non può avviarsi, stampa una riga che dice il motivo, e poi esce. Per la maggior parte delle cause la riga contiene [runner:fatal]. Per alcune cause la riga inizia con error: invece, incluso quando il runner non può analizzare i suoi flag, non può leggere il segreto dell'ambiente, o non può creare o scrivere nella directory di base. La riga successiva quindi punta a --help.
La maggior parte delle righe di log inizia con un timestamp e [self-hosted-runner], che l'esempio seguente omette. Ad esempio, un runner avviato con il git proxy Anthropic e una capacità superiore a uno stampa una riga come questa:
[runner:fatal] --use-anthropic-git-proxy requires --capacity 1 (the proxy URL is per-session and linked worktrees share origin). Omit --use-anthropic-git-proxy or set --capacity 1.
Cercare la riga nell'output standard e nell'errore standard del runner, nei log del container della piattaforma, o nel file impostato con --log-file. Il runner stampa una riga error: prima di aprire il file di log, quindi cercarla nel terminale o nei log del container, come Troubleshooting nota.
Questi aiutano anche quando si legge un avvio fallito:
- Nessuna riga affatto: un runner che l'host uccide non stampa nessuno. Se l'output termina senza riga
[runner:fatal]e senza rigaerror:, controllare se l'host o l'orchestrator ha fermato il processo, ad esempio per aver superato un limite di memoria. - Il codice di uscita: il runner non mette da parte un codice di uscita per gli errori che si ripetono ad ogni avvio. Esce con lo stesso codice per un errore di configurazione, come una combinazione non supportata di flag, e per un errore che può chiarirsi da solo, come l'API che rimane irraggiungibile attraverso i propri tentativi del runner. Basare la decisione di attendere più a lungo su quanto velocemente il runner è uscito, e leggere l'output del runner per imparare il motivo.
- Un ambiente che sembra sano: alcuni passaggi di avvio vengono eseguiti dopo che il runner si registra con l'ambiente, come
--configure-gite la configurazione delle credenziali del git proxy Anthropic. Se uno di questi passaggi fallisce, l'ambiente può continuare a elencare quel runner per alcuni minuti dopo che il processo è uscito, e la pagina Cloud environments può leggere Healthy mentre nessun runner sta raccogliendo lavoro. Se le sessioni rimangono in coda in un ambiente che sembra sano, controllare se il supervisore sta riavviando il runner.
Restart with a wait that grows
Come ottenere un'attesa crescente dipende dal supervisore.
-
Kubernetes: il Deployment su questa pagina non necessita di alcun cambiamento. Dopo che un container esce, il kubelet per impostazione predefinita attende prima di riavviare il container, e l'attesa cresce ad ogni riavvio fino a un limite. L'attesa ricomincia una volta che il container è stato eseguito per un po' senza uscire.
Il kubelet applica la stessa attesa dopo un'uscita normale quando il container è stato eseguito solo brevemente. Un runner che si scarica spesso può quindi mostrare anche lo stato
CrashLoopBackOff, quindi leggere l'output prima di concludere che il runner non può avviarsi. Il comando seguente legge l'output dell'ultima esecuzione da un pod del Deployment:kubectl logs --previous -n claude-runners deploy/claude-runnerQuando l'ultima esecuzione è stata un avvio fallito, la riga
[runner:fatal]oerror:è tra le ultime righe dell'output. Per leggere l'ultima esecuzione di un altro pod, nominare quel pod al posto dideploy/claude-runner. -
Docker e Docker Compose: la ricetta Compose su questa pagina non necessita di alcun cambiamento. Con
restart: always, Docker attende più a lungo prima di ogni riavvio di un container che continua a uscire, fino a un limite. Sostituire<container>con il nome del container nel comando seguente, che legge quante volte Docker ha riavviato il container:docker inspect --format '{{.RestartCount}}' <container>Il comando stampa un numero. Un numero che continua a salire significa che Docker continua a riavviare il runner.
-
Un'unità systemd: per impostazione predefinita systemd attende lo stesso
RestartSecprima di ogni riavvio e non lo allunga, quindi un'unità conRestart=alwaysriavvia un runner che non può avviarsi a quell'intervallo ogni volta. Quando gli avvii arrivano abbastanza velocemente da raggiungere il limite della velocità di avvio dell'unità, cinque avvii in 10 secondi per impostazione predefinita, systemd smette di riavviare l'unità. L'unità rimane ferma fino a quando qualcuno non la riavvia, il che systemd consente una volta che l'intervallo del limite di velocità è passato o doposystemctl reset-failed. PoichéRestartSecsi applica a ogni riavvio, un valore più lungo ritarda anche il riavvio dopo un'uscita normale. Scegliere un valore che bilancia i due, e avvisare sul conteggio dei riavvii dell'unità. -
Un ciclo shell o il proprio supervisore: applicare la stessa regola da soli. Iniziare con un'attesa di cinque secondi. Dopo ogni esecuzione che è terminata entro un minuto, raddoppiare l'attesa per il prossimo riavvio, fino a cinque minuti. Dopo un'esecuzione che è durata un minuto o più, tornare a cinque secondi.
Check why the runner keeps exiting
Quando il runner è uscito subito dopo l'avvio diverse volte di fila, fermarsi e controllare questi prima di riavviarlo di nuovo.
- L'ultima riga
[runner:fatal]oerror:: dice il motivo per cui il runner si è fermato. Troubleshooting elenca le cause comuni. - La combinazione di flag: il git proxy Anthropic richiede
--capacity 1. Le ricette su questa pagina utilizzano una capacità superiore, quindi abbassarla quando si aggiunge il proxy a una di esse. - Cosa può raggiungere l'ambiente del servizio: se il runner si avvia manualmente e fallisce sotto il supervisore, confrontare l'utente, la directory home, il
PATH, e il limite di memoria.--configure-gite il git proxy Anthropic hanno bisogno di git sulPATHe di un~/.gitconfigscrivibile. - Il segreto dell'ambiente: se è stato revocato il segreto o è stato digitato male, il runner stampa una riga che contiene
RegisterRunner auth failed. - La scheda Activity dell'ambiente: aprire l'ambiente e selezionare Activity. Se nuovi runner continuano ad apparire lì e nessuno raccoglie lavoro, il supervisore sta riavviando il runner.
Per una diagnosi guidata sull'host del runner, eseguire il subcommand doctor.
Una sessione viene presa in carico, ma Claude non risponde mai al primo messaggio
Se un runner prende in carico una sessione ma Claude non risponde mai al primo messaggio, e i tuoi runner raggiungono api.anthropic.com attraverso un proxy, la causa potrebbe essere il proxy. Anthropic invia a una sessione i suoi messaggi tramite un'unica risposta in streaming che rimane aperta. Se il tuo proxy la mette in buffer, o la chiude prima di inoltrare qualcosa, il primo messaggio non arriva mai. Le altre richieste della sessione continuano a riuscire.
Per risolvere, modifica il modo in cui il proxy gestisce questa richiesta:
GET https://api.anthropic.com/v1/code/sessions/<session-id>/worker/events/stream
- Non mettere in buffer la risposta: lasciala passare man mano che arriva. Il suo content type è
text/event-stream. - Lascia che la risposta resti aperta: può restare aperta fino a un'ora. Se il proxy ha un timeout di inattività, mantienilo ben al di sopra di 15 secondi, perché per impostazione predefinita Anthropic invia un keep-alive almeno con quella frequenza.
Poi avvia una nuova sessione e verifica che Claude risponda. Aumentare i timeout del runner non serve: attendi solo più a lungo prima che il runner arresti la sessione.
Confermare la causa nel log del runner
Questi passaggi valgono per i runner con Claude Code v2.1.287 o successivo, con il valore predefinito --log-level info. I runner meno recenti registrano queste righe più tardi, oppure non le registrano affatto.
- Attendi che trascorra
--startup-timeout-min, 15 minuti per impostazione predefinita. Il runner arresta quindi la sessione e registra una riga che contieneno child output fore termina con— releasing. - Cerca
SDKStartup: SSEnel log del runner. Queste righe seguono una rigafinished: interrupted, alcune righe dopo quella del passaggio 1. La maggior parte non ha un timestamp, quindi un raccoglitore di log potrebbe mostrarle altrove. - Trova la riga in questa tabella:
| La riga di log contiene | Cosa fare |
|---|---|
SSE connect failed (no response in 30s) o SSE liveness timeout |
Verifica che il proxy inoltri la richiesta, lasci passare le intestazioni e i dati della risposta man mano che arrivano e non interrompa la connessione |
SSE connect failed (HTTP e un codice di stato |
Controlla il log del proxy stesso. Se il proxy ha risposto direttamente alla richiesta, lascia passare la richiesta attraverso di esso. |
SSE connect failed ( e qualsiasi altro testo |
Il testo tra parentesi è l'errore. Cerca la stessa connessione nel log del proxy stesso. |
SSE stream ended by server, ripetutamente |
Controlla il limite di tempo del proxy per le risposte di lunga durata. Una singola riga di questo tipo è normale. |
SSE reconnected after |
Cerca una delle altre righe dopo di essa. Questa riga non significa che qualcosa abbia raggiunto la sessione. |
Se il log non contiene nessuna di queste righe, o il log del proxy non mostra nulla di anomalo, segnala un problema.
Impedire alla sessione di riprovare
La sessione mantiene il messaggio in sospeso, e un runner che può servire il proprietario della sessione la prende di nuovo in carico. Finché non modifichi il proxy, la sessione resta in attesa nello stesso modo ogni volta. Per evitarlo, archivia la sessione in claude.ai/code.
Cosa c'è dopo
- Personalizza le sessioni: script wrapper, lifecycle hook, runner on-demand, server MCP, e permessi
- Testa end to end: verifica una nuova immagine runner da CI prima di promuoverla
- Riferimento: ogni flag CLI, variabile di ambiente, e metrica