Ambientes auto-hospedados estão em beta pública em planos Team e Enterprise; Disponibilidade e limitações cobre o caminho de habilitação. Esta página cobre a execução da frota em produção; consulte o guia de início rápido para seu primeiro runner e sessão.
Endurecimento de sua implantação
Um runner auto-hospedado executa código arbitrário direcionado pelo modelo em sua infraestrutura em nome de todos que podem enviar uma sessão para seu ambiente. Isso é qualquer membro de sua organização Anthropic, e qualquer pessoa que possa iniciar uma sessão de canal Claude Tag em um escopo que um Owner roteou para o ambiente. Trabalhe através de cada item antes de conectar um ambiente a sistemas de produção:-
Contêineres efêmeros por sessão: execute cada processo runner em um contêiner ou VM fresco que é destruído quando o processo sai, com
--capacity 1e o padrão--drain-grace-sec 0para que cada contêiner sirva exatamente uma sessão. Em uma capacidade mais alta, ou com uma graça de drenagem positiva, um contêiner serve múltiplas sessões do mesmo owner bloqueado; consulte Ciclo de vida do Runner. Não reutilize um sistema de arquivos entre reinicializações do runner, exceto na configuração deliberada de checkout pré-aquecido, e nunca entre owners.- Quando o runner interrompe uma sessão, ele não envia nenhum sinal para um processo que ainda esteja em execução depois que seu comando shell foi encerrado, como um serviço que foi daemonizado. Destruir o contêiner ou a VM encerra esse processo.
-
Sem credenciais amplas na imagem: não inclua chaves SSH de longa duração, credenciais de provedor de nuvem ou tokens de acesso pessoal que concedem mais do que uma sessão precisa. Crie credenciais usadas durante uma sessão, como tokens de push ou API, por sessão a partir de seu script wrapper. O clone inicial acontece antes do wrapper ser executado, então trate-o com um hook de ciclo de vida
checkout, ou com--use-anthropic-git-proxyquando todos os repositórios de uma sessão estiverem no github.com. Para ambos, consulte Configurar git. -
Mantenha as credenciais GitHub do host longe das sessões: Claude pode usar qualquer credencial GitHub que uma sessão consiga ler, com qualquer acesso que essa credencial conceda. Mantenha as próprias credenciais GitHub de escopo amplo do host runner fora de qualquer coisa que uma sessão possa ler. Tal credencial pode ser um token de acesso pessoal, o token que
gh auth loginsalva para sua conta, ou umGH_TOKENno ambiente do runner.- Com git gerenciado pela Anthropic: com tal credencial, Claude acessa o GitHub diretamente em vez de passar pelo git gerenciado pela Anthropic.
- Sem git gerenciado pela Anthropic: uma credencial de clone pode permanecer na imagem se você restringir seu escopo tanto quanto Incluir a configuração do git em sua imagem descreve.
- Mantenha o segredo do ambiente fora dos hosts que executam sessões: o segredo do ambiente pode registrar runners e pegar qualquer sessão enfileirada no ambiente. Em uma frota fixa, ele vive em cada host runner, onde o código de qualquer sessão pode ler o arquivo secreto. Prefira runners sob demanda, onde o segredo fica no host do orquestrador, que nunca executa código do usuário, e cada runner recebe uma ordem de trabalho de uso único que registra exatamente um runner. Em uma frota fixa, trate o arquivo environment-secret como legível por cada sessão e gire o segredo após qualquer suspeita de comprometimento de sessão.
- Saída de rede padrão-negar: restrinja o tráfego de saída do contêiner runner e sessão no seu próprio limite de rede em cada ambiente; Saída padrão-negar cobre o que permitir e por quê.
- IAM de host com privilégio mínimo: a identidade de computação anexada ao host runner, como um perfil de instância ou conta de serviço de nó, deve conceder apenas o que o próprio runner precisa. As sessões devem obter suas próprias credenciais através de seu script wrapper em vez de herdar a do host.
-
Bloqueie o endpoint de metadados da nuvem das sessões: manter as sessões fora da identidade do host requer bloquear seu acesso ao endpoint de metadados, e as políticas de saída no nível de sub-rede não interceptam o tráfego de metadados link-local, então bloqueie-o no próprio contêiner:
- IMDSv2 com um limite de salto de um
- GKE Workload Identity com ocultação de metadados
- Uma negação explícita para
169.254.169.254no namespace de rede do contêiner da sessão
-
Isolamento de sistema de arquivos por runner: cada processo runner obtém seu próprio diretório de trabalho que nenhum outro processo no host pode ler ou escrever. Faça
--hooks-dir, o script wrapper, e o~/.claude/do host somente leitura para a sessão, seja construído na imagem ou montado como somente leitura. -
Dispatch não tem controle de acesso por ambiente: qualquer membro de sua organização Anthropic pode enviar uma sessão para qualquer um de seus ambientes. Se um Owner rotear canais Claude Tag para o ambiente, qualquer pessoa que a configuração de acesso Claude Tag admita pode iniciar sessões de canal que são executadas lá. Por padrão, isso é qualquer pessoa no workspace Slack conectado, com ou sem uma conta Claude. Trate cada host runner como alcançável para execução de código por todos que podem enviar para ele, e coloque no host runner apenas dados e credenciais que todas essas pessoas têm permissão para ler.
--lock-to-accountlimita qual conta as sessões de um determinado host executam, mas não reduz quem pode enviar para o ambiente. Para tornar ambientes auto-hospedados a única opção de seletor, um Owner pode ocultar ambientes hospedados pela Anthropic para toda a organização na página Cloud environments. -
Enforce the repo-settings guard: escolha o modo de guard com
--confine-repo-settings. O padrãowarnregistra uma violação e ainda assim gera a sessão,enforcerecusa a sessão, eoffdesabilita a varredura. O runner verifica as configurações confirmadas de cada repositório para:- Uma concessão que se resolve fora do workspace da própria sessão: uma entrada
additionalDirectories, uma regraEdit,Write, ouNotebookEditempermissions.allow, ou uma entradasandbox.filesystem.allowWriteouallowRead - Um bloco
envnão vazio - Uma substituição de postura do operador, como
sandbox.enabled: false
--trust-workspace, e não cobre hooks de repositório,.mcp.json, ou regras Bash; consulte Permissões e aprovação de ferramentas para onde essas concessões pertencem. - Uma concessão que se resolve fora do workspace da própria sessão: uma entrada
Se sua organização tiver a allowlist de IP ativada, adicione os endereços públicos de saída de seus runners e contêineres de sessão à allowlist antes de iniciá-los. Se você executar runners sob demanda, adicione também o endereço do host do orquestrador. Não confie na allowlist como um controle de rede para tráfego de runner ou sessão. Em vez disso, aplique saída padrão-negar no seu próprio limite de rede.
Requisitos de rede
O runner e os filhos da sessão que ele gera fazem conexões de saída para os hosts abaixo. Restrinja a saída do contêiner de sessão a esses hosts e aos serviços internos específicos que as sessões precisam alcançar; Saída padrão-negar cobre como e por quê. Estes hosts são sempre necessários:
Um runner que usa
--use-anthropic-git-proxy roteia seu tráfego git de github.com através de api.anthropic.com, então ele não precisa do caminho para o host git github.com. Ele ainda precisa desse caminho se você definir --push-outcome-on-release ou fizer push a partir de um hook post-session.
Se esses hosts são necessários depende de sua configuração:
Você não precisa adicionar estes hosts à allowlist para tráfego de runner ou sessão:
statsig.anthropic.com,*.sentry.io,claude.aieplatform.claude.com: esses hosts aparecem em algumas listas de verificação de rede corporativa mais antigas, mas o runner não os alcança. As buscas de sinalizadores de recursos vão paraapi.anthropic.com, e o runner se autentica com o segredo do ambiente em vez de OAuth interativo.mcp-proxy.anthropic.com: sessões auto-hospedadas não o usam. Quando a entrega de conectores está habilitada para sua organização, os conectores claude.ai de sua organização alcançam as sessões através deapi.anthropic.com. Consulte Servidores MCP.
claude.ai, então execute-os a partir de um host cuja saída permita isso, em vez de ampliar a saída do contêiner de sessão:
- O instalador de uma linha: busca
install.shdeclaude.aino tempo de instalação. claude auth logininterativo: faz login através declaude.ai,claude.comeplatform.claude.com. A configuração guiada, o modo com login dodoctore o dispatch de CI o usam. O navegador com o qual você faz login também carrega as verificações de navegador da página de login do claude.ai a partir dehcaptcha.com,*.hcaptcha.comechallenges.cloudflare.com. Se sua organização usa login único, esse navegador também pode ser redirecionado através desso.claude.com.
Saída padrão-negar
Implante contêineres de runner e sessão em um segmento de rede ou namespace cuja saída é limitada aos hosts na tabela de requisitos de rede, seu host git, e os serviços internos específicos que as sessões precisam alcançar. O produto não pode verificar ou aplicar isso, então aplique-o no seu próprio limite de rede em cada ambiente. O código da sessão é direcionado pelo modelo e pode tentar conexões com hosts arbitrários; a saída padrão-negar no nível de rede limita onde essas tentativas podem chegar. Isso se aplica independentemente do modo de permissão: o conjunto de ferramentas pré-aprovado padrão já incluiBash, então a saída do shell é executada sem um prompt mesmo sem modo automático.
Para detalhes sobre qual telemetria cada sessão emite e como desativá-la, consulte Telemetria.
Autenticar em um proxy de saída
Alguns proxies de saída corporativos exigem um cabeçalhoProxy-Authorization em cada conexão. O token nesse cabeçalho geralmente gira muito rápido para ser escrito na URL do proxy que você define em HTTPS_PROXY. Defina HTTPS_PROXY ou HTTP_PROXY para a URL do seu proxy como de costume, então defina --proxy-authorization-command ou --proxy-authorization-file para dizer ao runner onde ler o valor do cabeçalho. Ambos os sinalizadores exigem Claude Code v2.1.238 ou posterior.
Escolha de onde o valor Proxy-Authorization vem
Escolha o sinalizador que corresponde a como você produz o token Proxy-Authorization:
--proxy-authorization-command <command>: escolha isso para um token que você gera sob demanda. O runner executa o comando shell e usa sua stdout aparada como o valor do cabeçalho, por exemploBearer <token>.--proxy-authorization-file <path>: escolha isso para um token que outro processo gira no lugar. O runner lê o arquivo e usa seu conteúdo aparado como o valor do cabeçalho.
Configurações que o runner se recusa a iniciar com
Cada sinalizador também tem uma forma de variável de ambiente, listada ao lado dele na referência de sinalizadores CLI do runner. Antes do runner entrar em contato com seu proxy ou o plano de controle, ele verifica os sinalizadores e suas variáveis, e se recusa a iniciar em três casos:- Ambos os sinalizadores definidos: um sinalizador mais a variável de ambiente do outro sinalizador conta como definir ambos.
- Nenhuma URL de proxy: nem
HTTPS_PROXYnemHTTP_PROXYcontém uma URLhttp://ouhttps://. O runner lê ambas as variáveis em maiúsculas ou minúsculas, e não consultaALL_PROXY. - Qualquer sinalizador passado para o subcomando orquestrador:
self-hosted-runner orchestratornão aceita os sinalizadores ou suas variáveis de ambiente. Passe o sinalizador para cada runner que o orquestrador inicia em vez disso.
O que o runner muda enquanto um sinalizador de autorização de proxy está definido
Com qualquer sinalizador definido, o runner inicia um listener próprio e envia tráfego de proxy de si mesmo, seus hooks de ciclo de vida, e suas sessões através desse listener. O listener adiciona o cabeçalhoProxy-Authorization no caminho para seu proxy.
- Listener: o listener é um proxy direto em
127.0.0.1. O runner inicia o listener antes de se registrar no plano de controle, e sai na inicialização se o listener não puder iniciar. - Variáveis de proxy: o runner reescreve qual de
HTTPS_PROXYeHTTP_PROXYvocê definiu para que aponte para o listener. Esse valor reescrito alcança o próprio runner, seus hooks de ciclo de vida, e cada sessão que ele executa. - Rotação de token: um token girado entra em vigor sem uma reinicialização. Para cada conexão que o listener abre para seu proxy, o runner executa seu comando ou lê seu arquivo novamente e adiciona o resultado como o cabeçalho.
- Ambiente da sessão: uma sessão alcança seu proxy apenas através do listener. No ambiente de cada sessão, o runner remove
ALL_PROXY, remove qualquer grafia deHTTPS_PROXYouHTTP_PROXYque você não definiu, e fixaNO_PROXYao próprio valor do runner. - Logs: o runner nunca registra o valor do cabeçalho.
Configurar git
O runner gerencia checkouts de repositório, mas não configura identidade git ou credenciais por padrão. Você controla a imagem e o ambiente de processo do runner, então você controla a configuração git. Escolha uma de duas abordagens:- Deixe o runner configurar git: inicie o runner com
--configure-gitpara que ele escreva a mesma identidade e configuração de assinatura de commit que as sessões hospedadas pela Anthropic usam - Envie configuração git em sua imagem: defina identidade e credenciais de push você mesmo, por exemplo para fazer commit sob sua própria identidade de bot
--use-anthropic-git-proxy, ou definir CLAUDE_RUNNER_USE_GIT_PROXY=1, para pedir que a Anthropic sirva o git para as sessões do runner.
Pisos de versão Git no host runner: --configure-git a assinatura de commit SSH requer Git 2.34 ou mais recente, --use-anthropic-git-proxy requer 2.32 ou mais recente, e retomar sessões de branches enviados por --push-outcome-on-release requer 2.29 ou mais recente. Git 2.24 é suficiente se você omitir todos os três e gerenciar a identidade git você mesmo.
Deixe o runner configurar git
Inicie o runner com--configure-git, ou defina SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, para que ele escreva configuração git global na inicialização:
user.name = Claudeeuser.email = noreply@anthropic.com, correspondendo às sessões hospedadas pela Anthropic- Assinatura de commit e tag em formato SSH, roteada através de um shim gerenciado pelo runner que assina cada commit através do serviço de assinatura da Anthropic usando as credenciais da própria sessão. As assinaturas são verificáveis no GitHub contra a chave de assinatura SSH publicada da Anthropic.
push.negotiate = true, para que git pergunte ao seu host git quais commits ele já possui antes de empacotar um push. Requer Claude Code v2.1.257 ou posterior.core.hooksPathapontando para um diretório de hooks gerenciado pelo runner. Seus hookscommit-msgeprepare-commit-msgadicionam um trailerCo-authored-by:para o criador da sessão a cada commit. O trailer é construído a partir do email emCCR_SESSION_ACCOUNT_EMAILe omitido quando essa variável não está definida. Se sua imagem já definecore.hooksPathe o runner não usa git gerenciada pela Anthropic, o runner mantém sua configuração, pula a instalação desses hooks e imprime um aviso[runner:git].
checkout ou post-session também são assinados como a sessão, sem o trailer Co-authored-by:. Configuração git dentro de hooks de ciclo de vida descreve as configurações git que o runner fixa dentro desses hooks.
Com ou sem --configure-git, o Claude Code instrui o Claude a terminar suas mensagens de commit com um trailer Claude-Session: <url> e suas descrições de pull request com a URL da sessão. Para omitir ambos, defina attribution.sessionUrl como false no ~/.claude/settings.json do host runner e, em seguida, reinicie o runner.
Envie configuração git em sua imagem
A identidade Git é necessária para qualquer commit. Defina-a em todo o sistema em seu Dockerfile para que a configuração se aplique independentemente de qual usuário o processo runner é executado como:git commit falha com Please tell me who you are e as sessões não podem fazer progresso. Você pode usar sua própria identidade de bot em vez disso; o runner não substitui esses valores.
Não incorpore credenciais de push de longa duração ou de escopo amplo em uma imagem de runner compartilhada: uma credencial na imagem está disponível para cada sessão que a imagem executa, quem quer que a tenha iniciado. Em vez disso, crie um token de curta duração e escopo mínimo por sessão a partir de seu script wrapper, usando a identidade do criador da sessão decodificada do JWT da sessão. Emparelhe-o com um contêiner por sessão efêmero, que requer --capacity 1, para que nenhuma credencial sobreviva à sessão que a criou; consulte a seção de endurecimento.
Se você deve configurar credenciais de push no nível de imagem, por exemplo para uma chave de implantação somente leitura, escopo-as tão bem quanto seu host git permite:
- Uma chave de implantação SSH limitada a um repositório com uma reescrita
url.<base>.insteadOf - Um
credential.helperque retorna um token com escopo mínimo GIT_SSH_COMMANDapontando para uma chave com escopo restrito
- O runner define
GIT_TERMINAL_PROMPT=0, para que git não peça um nome de usuário ou senha. - O runner executa SSH com
BatchMode=yes, anexado ao seuGIT_SSH_COMMANDse você definir um, para que SSH não peça uma frase-passe ou confirmação de host. - O runner define
GCM_INTERACTIVE=never, para que Git Credential Manager não abra um diálogo de login. - O runner limpa
core.askPass, então se você usar um helper askpass, defina-o através da variável de ambienteGIT_ASKPASSem vez disso.
GIT_SSH_COMMAND ou GIT_ASKPASS onde as sessões não possam escrever nele, da forma que a lista de verificação de endurecimento pede para o diretório de hooks e o script wrapper. O mesmo vale para qualquer chave ou arquivo na linha de comando desse programa. O próprio git do runner executa esse programa quando clona ou busca.
Se os diretórios de checkout são possuídos por um uid diferente do processo runner, git se recusa a operar neles; adicione safe.directory:
Use o proxy git da Anthropic
Com o proxy git da Anthropic, também chamado de git gerenciada pela Anthropic, a imagem do runner não precisa de chaves SSH, credential helper,.netrc ou outras credenciais git para a própria sessão. Em vez disso, o runner pede que a Anthropic sirva o git para suas sessões. Para a sessão de um usuário que a Anthropic serve, o clone do runner e os próprios fetches e pushes da sessão passam pela Anthropic, que usa o token OAuth do GitHub armazenado para o criador da sessão. Como a Anthropic serve git para uma sessão cobre sessões de bot e agente.
O proxy git fica desativado, a menos que você o ative. Um runner que alcança seu host git com suas próprias credenciais não precisa dele, e seu git funciona com qualquer host git.
Em troca, o proxy git limita o que o runner suporta e muda o que ele precisa:
- Somente github.com: a Anthropic serve uma sessão apenas quando todos os seus repositórios estão em github.com, e o proxy git ainda não suporta GitHub Enterprise Server. Em um runner com o proxy git, uma sessão com um repositório em outro host git falha ao iniciar.
- Credenciais somente para os repositórios da sessão: a Anthropic fornece credenciais git para os repositórios que fazem parte da sessão, não para outros repositórios no mesmo host git. Um submódulo privado, uma dependência que seu gerenciador de pacotes busca com git ou um marketplace de plugins em outro repositório não recebe nenhuma credencial da Anthropic. Peça às pessoas que criam sessões que adicionem todos os repositórios de que uma sessão precisa ao criá-la. Cada execução de uma tarefa agendada na nuvem é uma sessão com os repositórios da tarefa, então peça ao proprietário de cada tarefa que adicione todos os repositórios de que suas execuções precisam.
- Somente pushes de branch: um push que exclui um branch falha, assim como um push para qualquer outro tipo de ref, como uma tag. Para saber quais branches um push pode atualizar, consulte Proxy do GitHub.
- Contas do GitHub conectadas: peça a cada pessoa que cria sessões de usuário com um repositório que conecte o GitHub. Sem a própria conexão, essas sessões não iniciam.
-
--capacity 1: o proxy git requer uma sessão por processo runner, então execute mais réplicas para paralelismo. Ative o proxy git da Anthropic lista os requisitos. - Configuração git global substituída: o runner exclui e substitui a configuração git global do usuário com o qual é executado. Execute-o como um usuário dedicado ou em um contêiner.
-
Credenciais do host para pushes do host: o push de
--push-outcome-on-releasedo runner e qualquer push que seu hookpost-sessionfaça ainda usam as próprias credenciais git do host runner e seu caminho de rede atégithub.com. Mantenha essas credenciais fora da configuração git global e consulte Envie configuração git em sua imagem para ver formas de fornecê-las. - Decisão por sessão: a Anthropic decide, para cada sessão no runner, se serve o git dela, e uma sessão que ela não serve falha ao iniciar. Quando as sessões falham ao iniciar em um runner com o proxy git cobre as causas.
safe.directory, na configuração git do sistema.
Ative o proxy git da Anthropic
Antes de iniciar o runner com--use-anthropic-git-proxy, confirme que o host runner atende a cada um destes requisitos. O runner se recusa a iniciar quando o requisito de capacidade ou de git não é atendido:
- Claude Code v2.1.267 ou posterior: versões anteriores aceitam a flag, mas não relatam a solicitação para que a Anthropic sirva o git nem imprimem a linha
Registering as opted in, então a Anthropic não serve suas sessões. --capacity 1, o padrão: cada processo runner lida com uma sessão por vez, então execute mais réplicas para paralelismo.- Git 2.32 ou mais recente: git mais antigo ignora a configuração git por sessão que o runner prepara para o proxy git.
--use-anthropic-git-proxy ao comando do runner ou defina CLAUDE_RUNNER_USE_GIT_PROXY=1 no ambiente do runner. Este comando, executado em um shell no host runner, inicia o runner do guia de início rápido com o proxy git ativado:
Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). A Anthropic então decide, para cada sessão nesse runner, se serve o git dela. Para cada sessão que ela serve, o runner registra uma linha [runner:session] contendo governed git ACTIVE. Se, em vez disso, uma sessão falhar ao iniciar, consulte Quando as sessões falham ao iniciar em um runner com o proxy git.
Como a Anthropic serve git para uma sessão
Para uma sessão que a Anthropic serve, o clone do runner e os próprios fetches e pushes da sessão passam pela Anthropic, autenticados com o token de curta duração da própria sessão:- Sessões de usuário: a Anthropic usa o token OAuth do GitHub armazenado para o criador da sessão.
- Sessões de bot e agente: a Anthropic usa o token de instalação do GitHub App de sua organização.
- Reescritas de URL:
--git-host-rewritee--git-ssh-rewritenão têm efeito em um repositório que o proxy git serve.
Quando as sessões falham ao iniciar em um runner com o proxy git
Em um runner iniciado com--use-anthropic-git-proxy, uma sessão falha ao iniciar quando a Anthropic não serve o git dela. Procure no log do runner um erro git que nomeie um endereço api.anthropic.com contendo /git_proxy/.
Para cada sessão, um runner no Claude Code v2.1.267 ou posterior também registra uma linha [runner:session] contendo governed git ACTIVE quando a Anthropic serve o git da sessão, ou uma linha [runner:warn] contendo the server withheld Anthropic-managed git for this session quando não serve. Encontre a linha que você está vendo entre estes casos:
- Nem
governed git ACTIVEnem a linhawithheld: um runner anterior ao Claude Code v2.1.267 não registra nenhuma das linhas, e a Anthropic não serve suas sessões. Atualize o runner para a v2.1.267 ou posterior seguindo Fixe a versão. - A linha
withheld: a Anthropic não serviu a sessão. Um runner que funcionava antes com o proxy git pode falhar dessa forma sem nenhuma mudança do seu lado.- Um repositório não está em github.com: uma sessão com mesmo um único repositório em outro host git, como GitHub Enterprise Server, não é servida, incluindo seus repositórios em github.com. Desative o proxy git da Anthropic para os runners desse ambiente.
- Todos os repositórios estão em github.com: relate a falha à sua equipe de conta da Anthropic com o ID da sessão da linha
withheld. A Anthropic registra o motivo do lado dela.
- Uma linha contendo
remote: access denied by the git proxy: uma sessão que a Anthropic serve ainda pode ser recusada, por exemplo quando a política da organização nega acesso git para a sessão, ou a sessão não está autorizada para o repositório. O log do runner então mostra uma linha contendoremote: access denied by the git proxy, e o restante dessa linha diz o motivo. GitHub authentication required: isso aparece quando o criador da sessão não tem uma conexão funcional com o GitHub em claude.ai. O clone da sessão falha, e o erro git contémGitHub authentication required. Please reconnect your GitHub account.Peça a essa pessoa que conecte ou reconecte o GitHub nas configurações do claude.ai.
Desative o proxy git da Anthropic
Se as sessões em um ambiente usam um repositório em um host git diferente de github.com, como GitHub Enterprise Server, desative--use-anthropic-git-proxy para os runners desse ambiente.
1
Remova a flag
Remova
--use-anthropic-git-proxy do comando do runner. Se você definiu CLAUDE_RUNNER_USE_GIT_PROXY no ambiente do runner, como em uma especificação de pod ou em um arquivo Compose, remova-a de lá. Em um shell, remova a definição dela:2
Forneça credenciais git ao runner
Forneça credenciais que funcionem sem um prompt para cada host git que as sessões dos runners usam, incluindo github.com. Qualquer credencial que estava na configuração git global do usuário do runner se perdeu, porque o runner excluiu essa configuração enquanto
--use-anthropic-git-proxy estava definido. Envie credenciais em sua imagem ou use um hook de ciclo de vida checkout.3
Abra o caminho de rede
Permita que o runner alcance cada host git que as sessões dos runners usam na porta 443 ou 22. Consulte a linha do host git em Requisitos de rede.
4
Reinicie os runners
Reinicie os runners para que eles se registrem sem o proxy git. Em seguida, inicie novamente cada sessão que falhou.
Acesso à API do GitHub sem o GitHub CLI
Se a imagem do seu runner não inclui o GitHub CLI, o Claude Code pode fornecer umgh integrado, para que o Claude ainda possa abrir pull requests, comentar e ler resultados de CI. O gh integrado é para runners que usam git gerenciada pela Anthropic. Ele suporta um comando, gh api, que chama a REST API do GitHub. Requer Claude Code v2.1.287 ou posterior na imagem do runner.
Este comando abre um pull request no lugar de gh pr create. O gh integrado preenche {owner} e {repo} para o repositório atual:
- Credenciais: o
ghintegrado envia suas requisições REST através da git gerenciada pela Anthropic, que fornece a credencial do GitHub do lado da Anthropic, então a imagem não precisa de nenhum token do GitHub para ele - Quais sessões o recebem: a Anthropic decide por sessão se a git gerenciada pela Anthropic atende o
ghda sessão. Quando atende, a linha[runner:session] governed git ACTIVEque o runner registra para a sessão mostragh_path_shim=true. Quando não atende, a sessão não temgh jq: instalejqna imagem se você quiser que--jqfuncioneCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: se o ambiente da sessão a define, o Claude Code não fornece oghintegrado, e a sessão não temgh
Confie em uma autoridade de certificado privada com git gerenciada pela Anthropic
Esta seção se aplica se você definirGIT_SSL_CAINFO ou GIT_SSL_NO_VERIFY no ambiente de um runner cujas sessões usam git gerenciada pela Anthropic. O tratamento que ela descreve requer que o runner execute Claude Code v2.1.283 ou posterior.
Quando git no runner deve confiar em uma autoridade de certificado privada (CA), como aquela que um proxy de inspeção TLS assina, as abordagens usuais funcionam da seguinte forma:
- Armazenamento de certificado do sistema: instale sua CA no armazenamento de certificado do sistema do host runner, e git confia nela sem nenhuma variável.
GIT_SSL_CAINFO: defina-a para um arquivo PEM de suas CAs, por exemploGIT_SSL_CAINFO=/etc/ssl/corp-ca.pem.GIT_SSL_NO_VERIFY: não ajuda atrás de um proxy de re-assinatura. O clone do runner através de git gerenciada pela Anthropic verifica certificados mesmo quando a variável está definida, então esse clone falha até que git confie em sua CA através de uma das outras duas abordagens.
command começa com o ambiente da sessão, então ele obtém o que git dentro da sessão obtém:
GIT_SSL_CAINFO: o que git verifica git gerenciada pela Anthropic depende de onde git é executado:- Clone e fetches do runner: executados sem a variável e verificam git gerenciada pela Anthropic contra um arquivo de certificado por sessão que o runner escreve. O runner constrói esse arquivo a partir do pacote de CA do sistema do host runner e dos certificados do seu arquivo.
- Git dentro da sessão: obtém configuração
http.sslCAInfonomeando seu arquivo no lugar da variável, mais entradashttp.<url>.sslCAInfoque verificam git gerenciada pela Anthropic contra o arquivo por sessão. - Hooks
checkoutepost-session: herdam a variável inalterada.
GIT_SSL_NO_VERIFY: quais verificações de certificado permanecem desligadas depende de onde git é executado:- Clone e fetches do runner: executados sem a variável e verificam o certificado que lhes é apresentado.
- Git dentro da sessão: obtém configuração
http.sslVerify=falseno lugar da variável, então as verificações permanecem desligadas para outros hosts. Ele também obtém entradashttp.<url>.sslVerify=trueque mantêm as verificações ligadas para git gerenciada pela Anthropic. - Hooks
checkoutepost-session: quando a sessão tem um repositório em git gerenciada pela Anthropic, obtêm configuraçãohttp.sslVerify=falseno lugar da variável. Eles também obtêm entradashttp.<url>.sslVerify=trueque mantêm as verificações ligadas para git gerenciada pela Anthropic.
/etc/ssl/certs/ca-certificates.crt ou /etc/pki/tls/certs/ca-bundle.crt no host runner. Ele também precisa de um arquivo GIT_SSL_CAINFO que o usuário do runner possa ler, que contenha blocos CERTIFICATE PEM, e que tenha no máximo 1 MiB. Quando o runner não consegue construir o arquivo por sessão, ele registra uma linha [runner:warn] contendo did not build the certificate file e o motivo. Git então usa seu arquivo como está para git gerenciada pela Anthropic. Corrija o que a linha nomeia.
Para cada sessão que usa git gerenciada pela Anthropic, o runner também registra uma linha [runner:warn] que começa com governed git: GIT_SSL_CAINFO is set ou governed git: GIT_SSL_NO_VERIFY is set. A linha diz o que o runner fez com essa variável para seu próprio git, para git dentro da sessão, e para seus hooks de ciclo de vida. Ela termina com se você precisa alterar algo.
Adicione repositórios do github.com a sessões de canal do Claude Tag
Se o Claude não conseguir adicionar um repositório do github.com a uma sessão de canal do Claude Tag, certifique-se de que o canal tem acesso a ele, mesmo quando seu runner consegue cloná-lo. Esse acesso vem de configurações no GitHub e em sua organização do Claude, não das credenciais git do seu runner.1
Instale o Claude GitHub App
Instale o Claude GitHub App na organização ou conta do GitHub que é proprietária do repositório e inclua esse repositório na instalação.
2
Vincule a organização ou conta do GitHub
Em Organization settings > Git providers, vincule essa organização ou conta à sua organização do Claude. Um Owner da sua organização do Claude que também seja proprietário da organização do GitHub pode vinculá-la. Consulte Link your GitHub organization.
3
Conceda o repositório
Nas configurações de administrador do Claude Tag, conceda o repositório ao canal ou a um escopo mais amplo que inclua o canal: seu workspace do Slack ou todo o Slack. Alguém com a função Owner ou a permissão Claude Tag Admin pode concedê-lo. Um gerente de canal também pode adicioná-lo ao próprio canal se a conta do GitHub dele for administrador do repositório. Consulte Grant repository access.
Reescrever URLs git para redes privadas
URLs de repositório chegam do plano de controle como HTTPS, com o nome do host do seu host git; para GitHub Enterprise, esse é o nome do host que você configurou para a integração GitHub Enterprise em claude.ai. Duas flags repetíveis reescrevem essas URLs antes do clone:--git-host-rewrite <from>=<to>: para DNS de horizonte dividido, onde a Anthropic alcança seu host git através de um nome do host externo, mas runners devem usar um interno--git-ssh-rewrite <host>: para hosts git que apenas aceitam SSH, ou um host runner que tem apenas uma chave SSH. Por exemplo, para clonar repositórios do github.com via SSH, adicione--git-ssh-rewrite github.comao comando do runner. O runner então reescrevehttps://github.com/owner/repoparagit@github.com:owner/repo. O SSH deve conectar sem um prompt.
--git-ssh-rewrite se você precisar de ambos. Para controle total sobre checkout, use um hook de ciclo de vida checkout.
Construir a imagem do runner
A Anthropic não publica uma imagem de runner pré-construída. Construa a sua própria em torno do binárioclaude, camadas em qualquer toolchain que seus repositórios precisem: runtimes de linguagem, compiladores, gerenciadores de pacotes, e sidecars MCP.
As receitas abaixo usam --capacity 4, para que um contêiner sirva até quatro sessões simultâneas do mesmo owner bloqueado. Isso não fornece o isolamento de contêiner por sessão na seção de endurecimento: antes de conectar um ambiente a sistemas de produção, execute as receitas em --capacity 1 com um contêiner por sessão, ou use runners sob demanda, que também mantêm o segredo do ambiente fora dos hosts que executam sessões. Se você adicionar o proxy git da Anthropic a uma dessas receitas, também altere --capacity para 1.
Este Dockerfile é um ponto de partida mínimo. Ele instala python3 porque os hooks que o plano de controle da Anthropic fornece às sessões do Claude Tag, incluindo aquele que faz o Claude publicar sua resposta no Slack, executam python3. Mantenha python3 no PATH em qualquer imagem que atenda sessões do Claude Tag:
linux-x64 por linux-arm64 se seus nós forem ARM, ou por linux-x64-musl ou linux-arm64-musl em uma imagem baseada em musl, como Alpine; consulte Configuração Alpine Linux para os pacotes extras que imagens musl precisam. A URL é o local de lançamento padrão do Claude Code, para que você possa verificar o binário baixado contra o manifesto assinado do lançamento conforme descrito em Integridade binária e assinatura de código. O runner requer Claude Code versão 2.1.224 ou posterior, ou 2.1.229 ou posterior para atender sessões do Claude Tag. Construa a imagem, depois envie-a para seu registro e a referencie nas receitas abaixo:
stable atual e o passa como argumento de compilação, para que executar o mesmo comando após um novo lançamento estável reconstrua a camada de download com o binário mais recente. Para fixar um lançamento específico para compilações reproduzíveis, passe o número da versão diretamente como CLAUDE_CODE_VERSION. Substitua stable por latest na URL de pesquisa quando você precisar de um lançamento mais recente que o canal estável, como um que um modelo recém-lançado requer.
Dimensionar CPU e memória para sessões
Dimensione o contêiner ou host de um runner para as sessões que ele executa em vez de para o próprio processo runner. O runner em si faz polling para trabalho, prepara o checkout de cada sessão, executa seus hooks de ciclo de vida, e inicia e supervisiona os processos da sessão. A carga vem das sessões: cada uma é um processo Claude Code mais o que ela inicia, como builds, suites de teste, instalações de pacotes, e servidores MCP. Para uma sessão, comece com os seguintes valores, declarados como requisições e limites do Kubernetes ou o equivalente de sua plataforma, e trate-os como um ponto de partida em vez de um requisito:- Memória: uma requisição e um limite de 4 GiB cada, que atende ao mínimo de 4 GB nos requisitos do sistema do Claude Code. Mantenha os dois iguais para que o agendador contabilize a memória completa do contêiner. Quando o contêiner atinge seu limite de memória, o kernel mata processos dentro dele, o que pode encerrar uma sessão no meio da tarefa.
- CPU: uma requisição de 2 CPUs e um limite de 4 CPUs, para que uma sessão possa explodir acima da requisição durante builds. O kernel limita um contêiner em seu limite de CPU em vez de matar processos nele, então sessões no limite são executadas mais lentamente, mas continuam sendo executadas.
resources:
--capacity para limitar quantas sessões ele executa de uma vez. Ele não divide CPU ou memória entre elas, então as sessões em um runner compartilham a CPU e memória do contêiner. Para evitar que o uso de memória de uma sessão encerre as outras, execute uma sessão por runner com seu próprio limite de memória.
-
Uma sessão por runner: dê a cada contêiner os valores de uma sessão. Use este dimensionamento em
--capacity 1, que a seção de endurecimento recomenda, e para runners sob demanda, onde você define os valores na carga de trabalho que seu hookspawn-runnersubmete, como um modelo de pod de um Kubernetes Job. -
Várias sessões por runner: em um
--capacityacima de um, multiplique os valores de uma sessão pela capacidade, porque até muitas sessões podem ser executadas no contêiner ao mesmo tempo. As receitas Kubernetes e Docker Compose executam--capacity 4sem limites de CPU ou memória, então adicione limites dimensionados para a capacidade que você executa. -
Vários contêineres de runner em um host: dimensione o host para a soma das capacidades dos runners, porque cada runner conta apenas suas próprias sessões em relação ao
--capacity:- Memória: multiplique a memória de uma sessão pela soma e, em seguida, adicione o que os próprios processos do host usam
- CPU: multiplique a requisição de CPU de uma sessão pela soma. Para permitir que todas as sessões explodam ao mesmo tempo, multiplique o limite de CPU em vez disso.
--base-direm cada um, em um armazenamento que nenhum outro contêiner de runner monte, para que cada runner tenha seu próprio diretório de trabalho.
Dar a cada sessão seu próprio limite de memória
Para impedir que uma sessão encerre as outras quando a memória se esgota, execute cada runner em--capacity 1 sob seu próprio limite de memória, definido como a memória de uma sessão:
- Em um contêiner: execute um runner por contêiner e defina o limite de memória do contêiner, como
limits.memoryno Kubernetes,--memoryno Docker oumem_limitem um arquivo Compose. Mantenha a soma dos limites dos contêineres abaixo da memória do host, porque quando o próprio host fica sem memória, o kernel pode matar processos em qualquer lugar do host. - Sob systemd sem contêiner: defina
MemoryMaxna unit do runner e execute um runner por host, porque runners que usam o mesmo--base-direm um sistema de arquivos compartilham os repositórios clonados ali. Por exemplo, em--capacity 1sem hookcheckout, as sessões deles em um repositório podem trabalhar no mesmo checkout ao mesmo tempo. Para executar vários runners em um host, dê a cada um deles um contêiner.
--capacity acima de um, as sessões compartilham um único limite. Quando elas o atingem, o kernel não escolhe por sessão quais processos matar, então o uso de memória de uma sessão pode encerrar as outras.
No Kubernetes, você não pode separar as sessões com nenhum dos valores da configuração singleProcessOOMKill do kubelet. Você também não pode limitar a memória total de uma sessão com um ulimit de memória em seu script wrapper. O kernel aplica um valor como ulimit -v a cada processo separadamente.
Deixar espaço sob os limites de processos e threads
Para dimensionar um limite de processos e threads, meça o que uma sessão precisa e multiplique pelo número de sessões que compartilham o limite. Quando essas sessões atingem o limite, o kernel recusa novos processos e threads, então comandos, builds e testes em qualquer uma delas podem falhar. A chamada recusada retornaEAGAIN, cuja mensagem padrão é Resource temporarily unavailable.
Suponha que uma sessão atinja um pico de 300 processos e threads, e que quatro sessões compartilhem o limite. Juntas, elas precisam de 1.200 e, com um limite de 2.400, você deixa espaço para builds que precisem do dobro.
Onde definir cada limite
Cada limite nesta tabela conta threads assim como processos, epids.max é o arquivo que contém o limite de um cgroup:
Quando você não define
TasksMax, o systemd aplica DefaultTasksMax, cujo padrão é 15% do menor valor entre kernel.pid_max, kernel.threads-max e o pids.max do cgroup raiz.
Medir o pico de uma sessão
Para obter os números para seus próprios runners, meça uma sessão em seu host, que no Kubernetes é o nó:1
Inicie um runner isoladamente
Inicie um runner em
--capacity 1 sem mais nada em seu contêiner ou unit do systemd, sozinho em seu host e em seu ambiente:- Seu host: use um que não execute nenhum outro runner. Assim você poderá identificar o processo dele na próxima etapa.
- Seu ambiente: faça dele o único runner ali, por exemplo em um ambiente que você cria para o teste. As sessões são roteadas para um ambiente, e não para um runner.
2
Encontre o diretório cgroup do runner
No host, não dentro do contêiner, encontre o ID do processo do runner, por exemplo com
ps. Leia /proc/<pid>/cgroup para esse ID:- Uma linha contém
:pids:: o host usa cgroup v1, e o diretório é/sys/fs/cgroup/pidsseguido do texto após:pids: - A única linha começa com
0::: o host usa cgroup v2, e o diretório é/sys/fs/cgroupseguido do texto após0::
3
Execute um build representativo e registre o pico
Execute uma sessão durante um build representativo e obtenha o pico a partir do diretório cgroup do runner:
- O diretório tem
pids.peak: leia o arquivo após o término do build - O diretório não tem
pids.peak: leiapids.currentuma vez por segundo enquanto o build é executado e guarde o valor mais alto. Um pico entre duas leituras passa despercebido, então trate esse valor como uma estimativa baixa.
4
Multiplique pelas sessões que compartilham o limite
- O Pod, contêiner ou unit de um runner: multiplique o pico pelo
--capacitydo runner - Um limite que vários runners compartilham: multiplique o pico pela soma de suas capacidades
5
Defina o limite acima do resultado
Com um limite mais alto, você deixa espaço para builds mais pesados e, com um mais baixo, você interrompe uma fork bomb mais cedo.
Verificar um limite pids.max
- Verifique se
pids.maxfoi atingido: pesquise no log do kernel do host, por exemplo comdmesg, uma linha que contenhacgroup: fork rejected by pids controller ine o caminho de um cgroup. O kernel a registra em log apenas na primeira vez que recusa um processo ou thread nesse cgroup, então pesquise o log inteiro. - Veja o limite em vigor: leia
pids.maxno diretório cgroup do runner e em cada diretório acima dele. O menor valor é o limite em vigor, emaxsignifica nenhum limite nesse nível.
Kubernetes
O runner serveGET /healthz na porta 8080 por padrão, configurável com --health-port, então as sondas Kubernetes funcionam sem configuração extra. O endpoint retorna 200 sempre que o processo está vivo, então as sondas abaixo detectam um processo morto, não um preso; para pegar um runner que parou de fazer polling, alerte na série last_poll_age_seconds de /metrics. O Deployment abaixo monta o segredo do ambiente a partir de um Secret Kubernetes, aponta as sondas de vivacidade e prontidão para /healthz, e define um período de graça de encerramento de 90 segundos. Consulte Tempo de encerramento para entender por que o período de graça importa.
O manifesto não define resources de CPU ou memória no contêiner runner. Adicione um bloco dimensionado para a capacidade que você executa, conforme Dimensionar CPU e memória para sessões descreve.
claude-runners. Crie o namespace primeiro:
(umask 077 && cat > ./environment-secret), cole o segredo, pressione Enter, depois Ctrl-D. Depois crie o Secret e delete o arquivo:
Docker Compose
O serviço Compose abaixo reinicia o runner sempre que ele sai, o que cobre tanto crashes quanto a saída normal após drenagem. Uma política de reinicialização Docker reinicia o mesmo contêiner com sua camada gravável intacta, então o runner volta em um sistema de arquivos reutilizado em vez do fresco que a postura de endurecimento recomenda; use esta receita para avaliação, e para produção recrie o contêiner por execução ou use um orquestrador que faça. Docker aguarda mais tempo antes de cada reinicialização de um contêiner que continua saindo, até um limite máximo, então um runner que não consegue iniciar não continua reiniciando em um loop apertado sob esta receita. Quando o runner sai descreve o que verificar quando isso acontecer.Tempo de encerramento
ApósSIGTERM, um runner precisa de tempo para encerrar suas sessões de forma limpa antes que seu orquestrador o mate. Ele registra no log quanto tempo precisa na inicialização, em uma linha que contém This runner needs up to 80s nas configurações padrão. Defina o timeout de parada do seu orquestrador para pelo menos esse número de segundos: terminationGracePeriodSeconds no Kubernetes, stop_grace_period no Docker Compose, ou o equivalente da sua plataforma. O padrão do Kubernetes é 30 segundos, portanto sem essa configuração ele pode parar o pod antes que o runner termine.
Em SIGTERM, o runner para de aceitar novas sessões. Em seguida, ele inicia um encerramento gracioso, chamado de drenagem, a menos que você defina --defer-shutdown-max-min para adiá-lo. A drenagem tem três etapas:
- O runner aguarda até
--drain-wait-secsegundos,0por padrão, para que os turnos ainda em andamento sejam concluídos. - Ele encerra a árvore de processos de cada sessão, incluindo qualquer comando que Claude ainda estava executando, mas não um processo ainda em execução após o término do seu comando de shell.
- Ele executa o hook de ciclo de vida
post-session.
post-session ainda está salvando trabalho não confirmado.
O tempo registrado é a soma destes valores:
--drain-wait-secpara a etapa 1, 0 segundos por padrão--session-stop-grace-secpara a etapa 2, 5 segundos por padrão--post-session-hook-timeout-secpara a etapa 3, 60 segundos por padrão- Uma margem fixa de 15 segundos
- Mais 30 segundos quando
--push-outcome-on-releaseestá definido
--capacity maior não aumenta esse valor, porque o runner drena todas as suas sessões ao mesmo tempo.
Reserve mais tempo se você definir qualquer uma destas flags:
- Com
--retire-at: deixe tempo suficiente entre o tempo de aposentadoria e o tempo de parada do host para que turnos típicos sejam concluídos, mais a espera por tarefas em segundo plano que Ciclo de vida do Runner descreve, mais o tempo registrado. Calcule o tempo de aposentadoria em cada inicialização, por exemplodate +%smais o tempo de vida pretendido do runner. - Com
--defer-shutdown-max-min: o timeout de parada também deve cobrir os minutos que você configura e uma espera adicional antes do início da drenagem, 75 segundos nas configurações padrão. Adiar a drenagem após o primeiro sinal explica ambos. O runner também registra esse tempo maior na inicialização.
Permitir que os turnos em andamento terminem antes de uma reinicialização
Para permitir que os turnos em andamento sejam concluídos antes de uma reinicialização contínua, defina--drain-wait-sec e aumente seu timeout de parada para pelo menos essa espera mais 80 segundos. Com a espera padrão de 0, uma reinicialização interrompe qualquer turno ainda em andamento, e a sessão é retomada em outro runner sem seu trabalho não enviado.
--drain-wait-sec: aproximadamente a duração dos turnos e das tarefas em segundo plano que você deseja permitir que terminem. É um máximo: a espera termina antes se eles terminarem primeiro.- Seu timeout de parada:
terminationGracePeriodSecondsno Kubernetes,stop_grace_periodno Docker Compose. Por exemplo, com--drain-wait-sec 120, substitua o90na receita do Kubernetes ou do Docker Compose por200. - Se você alterou outras configurações de encerramento: os 80 segundos são para as configurações padrão. Inicie um runner com suas flags e use pelo menos o número na linha de inicialização
This runner needs up to, que inclui a espera. Com--defer-shutdown-max-mindefinido, use o número maior na linha de inicialização seguinte, apósSize the stop timeout to at least. Tempo de encerramento lista o que aumenta esses números.
Publicar uma mensagem de status antes de uma reinicialização
Antes de uma reinicialização que interromperá sessões, peça a um Owner que publique uma mensagem de status no ambiente a partir da página de administração Cloud environments. Em claude.ai/code, os membros veem a mensagem em suas sessões nesse ambiente. Peça ao Owner que defina uma expiração ao publicar, ou que remova a mensagem assim que os runners voltarem.Adiar a drenagem após o primeiro sinal
Defina--defer-shutdown-max-min <n> se você quiser que um runner que está reiniciando continue servindo as sessões que ele mantém por até n minutos, em vez de drenálas no primeiro sinal. No primeiro SIGTERM ou SIGINT, o runner para de aceitar novo trabalho e continua servindo as sessões que ele mantém. Ele continua sondando para que o plano de controle não recoloque essas sessões na fila. Requer Claude Code v2.1.238 ou posterior.
O que acontece com as sessões que o runner mantém após o primeiro sinal
Nos primeiros dois estágios que seguem o sinal, o runner libera sessões, e uma sessão liberada é retomada em um runner novo quando seu usuário envia sua próxima mensagem. Contando a partir do primeiro sinal, o runner passa por três estágios:- Pelos primeiros
nminutos: o runner serve suas sessões normalmente e continua aplicando--startup-timeout-mine--kill-session-after-min. Se você também definir--release-idle-session-min, o runner libera qualquer sessão cujo usuário tenha estado ocioso por esse tempo; sem isso, as sessões ociosas permanecem no runner. - Quando os
nminutos se esgotam: o runner libera todas as sessões que ainda mantém, ociosas ou não. O runner aguarda o término do turno de uma sessão no meio do turno e até 60 segundos adicionais para as tarefas de fundo de um turno, antes de liberar essa sessão. - Quando a carência pós-lançamento se esgota: o runner drena todas as sessões que ainda mantém, e o plano de controle recoloca cada sessão drenada na fila para outro runner imediatamente. A carência pós-lançamento começa quando os
nminutos se esgotam e é 75 segundos nos padrões. Se você definir--drain-wait-secacima de 60 segundos, a carência pós-lançamento será--drain-wait-secmais 15 segundos.
--defer-shutdown-max-min. Uma vez que uma drenagem está em andamento, o próximo sinal força a saída do runner. Isso vale se um segundo sinal ou a carência pós-lançamento se esgotando iniciou a drenagem.
Dimensionar o tempo limite de parada
Dê ao timeout de parada do seu host pelo menos a soma de três partes: osn minutos que você configura, a carência pós-lançamento e a drenagem que Tempo de encerramento descreve. Com as configurações padrão, a carência pós-lançamento é 75 segundos e a drenagem leva até 80 segundos, portanto permita n minutos mais 155 segundos. O runner imprime essa soma na inicialização sempre que --defer-shutdown-max-min está definido.
Se o timeout de parada se esgotar antes do runner terminar, as sessões que ele ainda mantém não recebem nenhum hook post-session. O runner não se desregistra, portanto o plano de controle pode levar alguns minutos, e às vezes mais, para recolocar essas sessões na fila. Se você não puder dar ao timeout de parada essa soma, deixe --defer-shutdown-max-min indefinido para que o runner drene no primeiro sinal.
O que atinge um hook post-session em execução
O hookpost-session e o filho da sessão Claude cada um executam em seu próprio grupo de processos POSIX, separado do runner, portanto os mecanismos de parada os alcançam de forma diferente:
- Um
SIGTERMenquanto o runner já está drenando: força a saída do runner imediatamente, pulando o que resta da drenagem. Sem--defer-shutdown-max-min, esse é o segundoSIGTERMque o runner recebe. Nada sinaliza um hookpost-sessionem execução no meio, portanto em um host simples onde um processo init adota órfãos, ele termina por conta própria, mas sem supervisão: seu orçamento de timeout não se aplica mais, e uma escrita no pipe de log fechado pode matá-lo comSIGPIPE, portanto um hook que precisa sobreviver a uma saída forçada lá deve redirecionar sua própria saída para um arquivo. Nas receitas de contêiner nesta página, o runner é o PID 1 do contêiner e sua saída encerra o contêiner, e sob o padrãoKillMode=control-groupdo systemd, a morte em nível de cgroup atinge o hook também, conforme a entrada Cgroup-wide kills descreve; em ambos, trate uma saída forçada como fatal para o hook e confie no período de carência. - Sinais em nível de grupo de processos, como
kill -- -<pid>em um script wrapper, controle de trabalho de shell ou um watchdog em nível de grupo: alcançam o runner e um subprocesso de hookcheckoutno meio, que permanece anexado ao grupo deliberadamente, mas não um hookpost-sessionem execução no meio ou o filho da sessão. - Mortes em nível de cgroup, como o padrão
KillMode=control-groupdo systemd ou oSIGKILLque o Kubernetes entrega para todo o contêiner quandoterminationGracePeriodSecondsexpira: alcançam tudo, incluindo o hook. O isolamento de grupo de processos não protege contra esses, razão pela qual o período de carência deve cobrir a drenagem completa. - O tempo limite próprio do hook: quando um hook excede
--post-session-hook-timeout-sec, o runner enviaSIGTERMpara todo o grupo de processos do hook, depoisSIGKILLdois segundos depois, portanto um worker que o hook bifurcou, como tar, rsync ou git, termina com o shell wrapper em vez de sobreviver como um órfão. A supervisão do runner termina uma vez que o stdio do hook fecha: um worker que redirecionou sua própria saída para um arquivo e sobrevive ao estágioSIGTERMestá além do alcance do runner.
post-session ainda estão em execução, portanto você pode distinguir uma drenagem silenciosa de uma que está no meio de um snapshot.
Mantenha o diretório base e a capacidade idênticos entre runners
Se um runner morre no meio de uma sessão, o servidor refileira a sessão e outro runner no ambiente a pega. Esse runner deriva o caminho de checkout de seu próprio--base-dir e --capacity: --capacity 1 faz checkout diretamente sob --base-dir, e um --capacity acima de 1 usa worktrees por sessão em vez disso. Quando runners no mesmo ambiente usam valores diferentes para qualquer sinalizador, o diretório de trabalho da sessão retomada muda, e caminhos absolutos que o agente registrou anteriormente, em edições, chamadas de ferramentas, ou suas próprias notas, apontam para um local que não existe mais.
Use o mesmo --base-dir e --capacity em cada runner em um ambiente, e não use um valor por host, como um ID de instância ou nome do host. Passe ambas as flags explicitamente, como fazem as receitas de Kubernetes e Docker Compose, para que nenhum dos valores dependa de um padrão.
O diretório base padrão é /workspace, com a exceção que a linha de referência --base-dir registra. O runner precisa de acesso de escrita a ele. Na inicialização, antes de se registrar, o runner cria o diretório e confirma que pode escrever nele, e sai com cannot create or write to base directory quando não pode. Um runner iniciado como root cria o /workspace padrão em si. Para um runner não-root, crie o diretório e dê ao usuário do runner a propriedade antes de iniciar o runner, ou aponte --base-dir para um diretório que esse usuário já possui.
Reuse a pre-warmed checkout
Para repositórios grandes, o clone pode dominar a inicialização da sessão. Para pular o clone frio, forneça você mesmo um clone no caminho onde o runner mantém o seu próprio. Sem um hookcheckout, o runner mantém um clone canônico por repositório em <base-dir>/<repo-owner>/<repo> e o reutiliza entre sessões:
- Em
--capacity 1: o runner busca a ref solicitada, destacaHEADe redefine duramente para ela, o que é quase instantâneo quando pouco mudou. - Em um
--capacityacima de um: o runner busca nesse clone e, em seguida, faz o checkout de um worktree separado a partir dele para cada sessão. Um clone pré-aquecido economiza o download, mas não o checkout.
- Clone na imagem: construa o clone em sua imagem de runner naquele caminho. Cada contêiner fresco então começa com o clone quente sem reutilizar um disco.
- Clone em um volume persistente: em runners que você pré-bloqueia para a conta de um usuário com
--lock-to-account, aponte--base-dirpara um volume persistente, para que o disco apenas sirva essa conta. Um runner pré-bloqueado nunca pega sessões de canal Claude Tag, então essa opção não se aplica a runners que as servem.
-
Profundidade do clone: crie o clone sem
--depth, com ou sem--single-branch. O runner nunca passa--depthao buscar em um clone com um remoteorigin, mesmo que você definaCLAUDE_RUNNER_FETCH_DEPTH. Com um clone completo, cada sessão baixa apenas os commits que faltam ao clone. Com um clone raso, uma sessão em um branch que divergiu antes do commit mais antigo do clone baixa todo o histórico desse branch. -
Mudanças rastreadas redefinem, arquivos não rastreados persistem: em
--capacity 1, cada sessão começa a partir de uma redefinição dura que limpa as modificações rastreadas da sessão anterior, mas o runner nunca executagit clean, então arquivos não rastreados das sessões anteriores do owner bloqueado permanecem na árvore. -
Diretórios por sessão também persistem: ao lado do checkout, o runner cria entradas por sessão sob
<base-dir>/_sessions/para cada sessão que executa. O diretório de configuração Claude da sessão contém uma cópia local da transcrição da conversa. Ao lado dele ficam os arquivos carregados da sessão, quando a sessão tem algum. O diretório da sessão também fica lá: ele contém quaisquer worktrees por sessão e checkouts do hookcheckoutenquanto a sessão é executada, e mantém tudo mais que Claude escreveu nele. Por padrão, o runner deixa esses em vigor quando a sessão termina, então em um disco que sobrevive ao processo do runner eles se acumulam. Cada sessão é executada como o próprio usuário do runner, então qualquer sessão posterior que o disco servir pode lê-los. Se você manter um--base-dirpersistente, dimensione o volume para esse crescimento. O mesmo se aplica a qualquer configuração que reinicie o runner no mesmo sistema de arquivos, incluindo a receita Docker Compose. -
Com
--remove-session-state, diretórios por sessão não persistem: inicie o runner com--remove-session-statepara que ele delete os diretórios por sessão de cada sessão conforme a sessão termina. A exclusão é do melhor esforço: os diretórios permanecem quando o runner é morto antes de sua limpeza ser executada. O clone canônico e arquivos que uma sessão escreveu em outro lugar no host, como o diretório temporário, permanecem independentemente. -
Com o proxy git, a redefinição se torna um checkout: com
--use-anthropic-git-proxy, o runner sanitiza o.git/do clone antes de cada sessão. Ele mantém o armazenamento de objetos e o estado raso, e deleta o índice. Sem o índice, cada sessão começa com um checkout completo do diretório de trabalho em vez de uma redefinição quase instantânea. O runner ainda busca no clone existente em vez de clonar o repositório novamente. Pré-aquecimentos de submódulo não são suportados sob o proxy. -
Com o proxy git, um pré-aquecimento
reftableperde suas refs: quando o runner sanitiza o.git/antes de cada sessão, ele mantém.git/refs/e remove o diretório.git/reftable/. Um clone pré-aquecido no formato de refreftabledo git perde suas refs dessa forma, então pré-aqueça o clone no formato de reffilesdo git. - Clones longos não precisam de workaround: o runner limita cada operação git com um watchdog de 120 segundos sem progresso e um limite duro de 30 minutos, não um tempo limite fixo, então um clone frio lento que continua relatando progresso é concluído.
Confirmar que o runner usa seu clone
Para confirmar que o runner usa seu clone, inicie-o com--log-level debug e inicie uma sessão no repositório. O runner registra em log Prefetched repo found at e o caminho do clone. Se, em vez disso, ele registrar No prefetched repo, fresh init at, não encontrou nenhum clone utilizável no caminho indicado nessa linha. Verifique se seu clone está nesse caminho, pertence ao usuário do runner e tem um remote chamado origin.
Se o runner encontrar seu clone e as sessões ainda iniciarem lentamente, verifique se o clone é raso.
Pin the version
O processo filho de cada sessão executa o próprio binário do runner por padrão, e o runner desativa auto-update dentro das sessões que gera. Com esse padrão, cada sessão executa a versão que você instalou no host ou incluiu no build da imagem. Uma atualização no nível do host entra em vigor na próxima vez que o runner inicia. Escolha qual versão suas sessões executam e quando ela muda:- Antes de fixar uma versão: verifique as versões do Claude Code que os modelos exigem para cada modelo que suas sessões usam. Se um modelo exigir uma versão mais recente do que aquela que suas sessões executam, o servidor rejeita requisições para esse modelo com Claude Code does not support this model.
- Para manter uma frota em uma versão: construa a imagem com uma versão fixada, ou em um host nu instale uma versão específica e desabilite auto-updates
- Para atualizar uma frota fixa: leia as entradas do changelog entre a sua versão e a que você está instalando, depois instale a versão mais recente ou recrie a imagem e reinicie os runners
- Para atualizar runners sob demanda: leia as entradas do changelog entre a sua versão e a que você está instalando, depois altere a imagem que o seu hook
spawn-runnerinicia. Cada novo runner recebe a nova versão. Um runner que já está em execução, incluindo um runner de reserva que--min-idleiniciou, mantém sua versão até encerrar. Não o reinicie, porque sua ordem de trabalho é de uso único. - Se as sessões iniciarem um binário diferente: se você usar
--exec-pathou um hookcommandpara iniciar um binário do Claude Code diferente do próprio binário do runner, as sessões executam a versão desse binário. Atualize esse binário quando atualizar o runner. - Plugins: marketplaces de plugin também não auto-atualizam; defina
FORCE_AUTOUPDATE_PLUGINS=1no ambiente do runner para deixar plugins auto-atualizarem enquanto o binário permanece fixado
Scale the fleet
Seu orquestrador decide quando adicionar ou remover runners. Por causa do bloqueio de um owner por runner, a contagem mínima de réplicas é o número de usuários e agentes Claude Tag que você espera estar ativos simultaneamente;--capacity controla paralelismo dentro das sessões de um owner, não entre owners.
Duas abordagens de dimensionamento estão disponíveis:
- Frota fixa: execute um conjunto estático de réplicas de runner e dimensione nas métricas Prometheus que cada runner serve
- Runners sob demanda: execute o subcomando
claude self-hosted-runner orchestrator, que faz polling na Anthropic para sessões que estão enfileiradas sem runner disponível e invoca seu hookspawn-runnerpara iniciar um por sessão. Consulte Runners sob demanda.
Liberar sessões ociosas em uma frota fixa
Se as sessões permanecerem enfileiradas em uma frota fixa enquanto todos os runners estão online, sessões ociosas podem ser a causa. Por padrão, uma sessão mantém seu slot depois que seu turno termina, e seu runner permanece bloqueado para o owner dessa sessão. Se a tarefa agendada na nuvem da sua equipe inicia uma nova sessão a cada execução, suas sessões ociosas podem ocupar todos os slots. Para verificar, leiaclaude_code_self_hosted_runner_session_idle_seconds nas métricas Prometheus de cada runner. Cada sessão que um runner mantém tem sua própria série: 0 enquanto a sessão está ocupada; caso contrário, os segundos desde que ela ficou ociosa. Escolha o tempo ocioso que você permitiria: mais longo do que as pausas que as pessoas fazem no meio de uma tarefa, e curto o suficiente para que cada execução seja liberada antes que a próxima execução da tarefa agendada comece. Compare as séries com ele.
- Séries acima desse tempo, como
1800para 30 minutos: sessões ociosas mantêm esses slots, então defina--release-idle-session-min - Todas as séries abaixo dele: nenhuma sessão ficou ociosa por tanto tempo. Adicione runners ou defina
--kill-session-after-minpara sessões que nunca ficam ociosas. - Um runner sem séries: nenhuma sessão o mantém. Consulte As sessões permanecem enfileiradas em Solução de problemas.
- Um runner cujas métricas não respondem: seu processo pode ter sido encerrado. Consulte Um ambiente que parece saudável em Quando o runner é encerrado.
--release-idle-session-min e esse tempo em minutos, como --release-idle-session-min 30. O runner então libera qualquer sessão que permaneça ociosa por esse tempo, seja ela iniciada por uma tarefa agendada ou por uma pessoa.
- Trabalho não enviado: uma sessão liberada é retomada na sua próxima mensagem, sem o trabalho que ela não havia enviado com push. Para manter seus commits, consulte Sessões retomadas perdem trabalho não enviado.
- Encerramento do runner: um runner que libera sua última sessão inicia um encerramento normal. Faça com que seu supervisor o reinicie em um sistema de arquivos novo, conforme descrito em Proteja sua implantação.
- Sessões que nunca ficam ociosas: defina também
--kill-session-after-min, porque algumas sessões não contam como ociosas
Problemas conhecidos e limitações
As seguintes são as limitações nesta versão, com workarounds onde um existe.Tráfego de conector sai de sua rede
A Anthropic chama ferramentas de conector a partir de sua própria infraestrutura em vez de seu runner. Ferramentas de conector são os conectores claude.ai, como GitHub, Slack e Linear. Quando Claude usa um conector em uma sessão auto-hospedada, esse tráfego passa porapi.anthropic.com em vez de originar dentro do seu limite de rede.
Para manter um conector fora de sessões auto-hospedadas, filtre-o com as configurações de política allowedMcpServers e deniedMcpServers. Claude Code aplica essas configurações aos conectores que a Anthropic entrega bem como aos servidores que você configura a partir do host do runner e aos servidores que os usuários adicionam, então se você implantar uma lista de permissões para outros servidores, Claude Code bloqueia conectores entregues também. Para manter conectores disponíveis ao lado de uma lista de permissões baseada em URL, adicione entradas que correspondam aos caminhos de proxy da Anthropic para conectores entregues:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
Algumas sessões não contam como ociosas
Uma sessão mantendo uma tarefa de fundo que nunca termina não conta como ociosa, então--release-idle-session-min não liberará o slot dessa sessão. Uma sessão que está aguardando uma aprovação solicitada de dentro de uma chamada de ferramenta em execução também não conta como ociosa. Sempre defina --kill-session-after-min ao lado dela como um backstop duro para que nenhuma sessão possa manter um slot indefinidamente.
--kill-session-after-min é um backstop para sessões descontroladas. Em um runner na v2.1.260 ou posterior, uma sessão que atinge o limite não é encerrada imediatamente. O runner oferece uma janela de graça, 15 minutos por padrão, que você pode alterar com SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS:
- Se a sessão está aguardando seu usuário, o runner a libera. Se sua vez terminou e ela mantém apenas tarefas de fundo, o runner aguarda até 60 segundos para que essas tarefas terminem e então a libera. A sessão retoma quando seu usuário envia sua próxima mensagem.
- Se uma vez ainda está em execução, o runner aguarda a vez terminar, ou para a sessão aguardar seu usuário em seguida, e então a libera.
- Se a sessão ainda estiver no runner quando a janela de graça terminar, o runner a encerra, e qualquer trabalho de uma vez em execução é perdido. Uma vez aguardando uma aprovação solicitada de dentro de uma chamada de ferramenta em execução é uma maneira de uma sessão ultrapassar a janela.
--kill-session-after-min 480 para 8 horas. Para liberar slots de conversas que ficam ociosas, use --release-idle-session-min em vez disso.
Limitações adicionais
- Sessões retomadas perdem trabalho não enviado: um runner novo clona o repositório novamente a partir de seu branch inicial, então o trabalho que a sessão não tinha enviado se foi.
- Para manter trabalho com commit: defina
--push-outcome-on-releaseem todos os runners do ambiente, porque um runner sem a flag retoma a sessão a partir de seu branch inicial. Um runner com a flag faz um push de melhor esforço dos branches de resultado da sessão antes de liberá-la, e a sessão retomada começa a partir desses commits. O push usa as próprias credenciais git do host do runner, inclusive em um runner que usa git gerenciado pela Anthropic. - Com um hook
checkout: repositórios obtidos por meio de um hook de ciclo de vidacheckoutnão recebem push. Em vez disso, faça um snapshot deles a partir do hookpost-session. - Antes de habilitar a flag: restrinja quem pode fazer push para refs
claude/*no remoto de origem. Na retomada, o runner busca o branch previamente enviado sem verificar quem o enviou. - Alterações sem commit ainda são perdidas: para solicitar que Claude faça commit e push quando terminar de responder, consulte Solicitar que as sessões enviem seu trabalho
- Para manter trabalho com commit: defina
- Um comando interrompido por um runner perdido pode ser executado novamente: quando a sessão é retomada em outro runner, Claude vê a chamada de ferramenta interrompida com um resultado informando que seu desfecho é desconhecido.
- Um repositório adicionado no meio da sessão pode falhar ao ser clonado: Claude o clona com
git clonevia HTTPS. Em um runner sem--use-anthropic-git-proxy, o clone falha com um erro de autenticação do git se nada no host puder ler o repositório. Sempre que possível, selecione cada repositório que a sessão precisa quando você a cria. - Alguns conectores não aparecem em sessões auto-hospedadas: um conector que você ainda não conectou nas Configurações do claude.ai não está listado em uma sessão auto-hospedada, e a sessão não o solicitará para conectar. Conecte-o nas Configurações primeiro, depois inicie uma sessão fresca. Adicionar um conector a uma sessão já em execução também não torna suas ferramentas disponíveis para Claude; inicie uma sessão fresca para pegar um conector recém-adicionado.
- Quando alguém arquiva ou exclui uma sessão, ou libera seu runner a partir do menu da sessão: não conte com o push de
--push-outcome-on-release. Para salvar commits nesses casos, faça push deHEADpara um branch de resgate a partir de um hookpost-session. Se você partir do exemplo ali, faça-o fazer push mesmo quando não houver nada sem commit, porque ele faz push apenas após um commit de snapshot.
Relatar um problema
Para problemas com ambientes auto-hospedados, entre em contato com sua equipe de conta Anthropic.Troubleshooting
Para diagnóstico orientado, execute o subcomando doctor no host do runner. O subcomando doctor inicia uma sessão interativa do Claude Code com os logs e o estado do runner anexados. Faça login comclaude auth login nesse host primeiro para que a sessão possa consultar seu ambiente, seus runners e suas sessões enfileiradas. Sem esse login, por exemplo quando o host se autentica com uma chave de API, fica limitado ao endpoint de saúde local, métricas e ao log do runner, e lê o log apenas se você iniciou o runner com --log-file.
-
Runner não aparece no ambiente: confirme que o host pode alcançar
api.anthropic.comvia HTTPS, o segredo do ambiente está atual e o relógio do host está dentro de cinco minutos da hora real; desvios maiores causam falha na autenticação. O runner registra[runner:fatal]com o motivo da rejeição em caso de falha de autenticação. -
Runner sai na inicialização com
cannot create or write to base directory: o runner não consegue criar ou escrever em--base-dir, que padrão é/workspace. Corrija a propriedade do diretório ou aponte--base-dirpara um caminho gravável, conforme descrito em Keep the base directory and capacity identical across runners. Se o runner registrar[runner:fatal]dizendo que a verificação do diretório base expirou, o diretório está em uma montagem NFS ou CSI travada. Verifique a saúde da montagem em vez de permissões. O runner imprime ambas essas falhas de inicialização para stderr antes de abrir--log-file, então procure por elas no terminal ou nos logs do contêiner da sua plataforma em vez do arquivo de log. Antes da v2.1.225, o runner não verificava o diretório base na inicialização, e essa configuração incorreta falhava nas sessões após a coleta. -
O runner sai na inicialização com
RegisterRunner failed:e um erro de certificado TLS: o erro pode serself signed certificate in certificate chain, por exemplo. O runner não confia na autoridade certificadora (CA) por trás do certificado que recebeu, como acontece atrás de um proxy que inspeciona TLS. DefinaNODE_EXTRA_CA_CERTSno ambiente do runner para um arquivo PEM com a CA raiz do proxy. Você não precisa defini-la novamente para as sessões, que a herdam. O Git não a lê: para clones através desse proxy, defina tambémGIT_SSL_CAINFO. -
Seu script de inicialização imprime um erro de escrita contendo
Resource temporarily unavailablee sai: seu script compartilha seu stdout ou stderr com o processoclaude self-hosted-runnerou do orquestrador que ele inicia, o qual mudou esse pipe ou socket compartilhado para o modo não bloqueante. Quando o pipe está cheio, uma escrita do seu script falha com esse erro do sistema operacional em vez de aguardar, e sobset -eo script sai. Para eliminar o erro, envie a saída do runner ou do orquestrador para um arquivo que o usuário do seu script possa criar. Acrescente>> "$HOME/claude-runner.log" 2>&1ao comando que o inicia, antes de qualquer&que o coloque em segundo plano:A saída do próprio script continua indo para onde ia antes, enquanto o que o runner escreve em stdout e stderr, incluindo seus erros de inicialização, vai para esse arquivo. Use esse redirecionamento do shell em vez de apenas--log-file, que espelha o log em um arquivo enquanto o runner continua escrevendo em stdout e stderr. Se você já passa--log-file, mantenha-a também, porque o redirecionamento não altera o que esse arquivo recebe. -
O orquestrador registra
poll failed:com um erro de certificado TLS e continua tentando novamente: o orquestrador não confia na CA por trás do certificado que recebeu, como acontece atrás de um proxy que inspeciona TLS. DefinaNODE_EXTRA_CA_CERTSno ambiente do orquestrador para um arquivo PEM com a CA raiz do proxy. Defina-a também para os runners que seu hookspawn-runnerinicia. Por exemplo, uma linha pode terminar compoll failed: self signed certificate in certificate chain — retrying in 1s, com as palavras da biblioteca TLS entre os dois-pontos e o travessão. Para confirmar a correção, verifique seconnectedétrueno corpo de/healthzdo orquestrador. -
Sessions stay queued: cada runner online pode estar bloqueado para um proprietário diferente. Verifique a métrica
claude_code_self_hosted_runner_locked_accountmetric de cada runner ou o campolocked_accountde sua linha de log[runner:health]para ver quem a mantém. Ambos mostram o email do proprietário apenas depois que o runner recebeu um token de sessão com uma reivindicaçãoact.email, que as sessões de um agente Claude Tag nunca fazem. Sem a reivindicação, o runner não emite nenhuma sérielocked_accounte registralocked_account=yes, o que informa que o runner está bloqueado, mas não para qual proprietário. Adicione réplicas ou aguarde um runner existente drenar e reiniciar. Se o ambiente usar runners sob demanda, verifique o orquestrador; consulte On-demand runners. -
As sessões falham imediatamente após a coleta: abra a sessão em claude.ai/code para ver o erro. Se o erro mostrar que o git não conseguiu se autenticar, verifique as credenciais git do runner. Em um runner iniciado com
--use-anthropic-git-proxy, consulte Quando as sessões não iniciam em um runner com o proxy git. Um diretório base não gravável interrompe o runner na inicialização em vez de fazer as sessões falharem. Consulte a entrada O runner sai na inicialização comcannot create or write to base directorynesta lista. -
Uma sessão é coletada, mas o Claude nunca responde à primeira mensagem: se seus runners alcançam
api.anthropic.comatravés de um proxy, configure-o para repassar a resposta/worker/events/streamda sessão à medida que ela chega. Aumentar os timeouts do runner não ajuda. Para confirmar a causa no log do runner, consulte Uma sessão é coletada, mas o Claude nunca responde à primeira mensagem. -
As sessões não iniciam em um runner que definiu
--use-anthropic-git-proxy: procure no log do runner poraccess denied by the git proxy, ou por um erro do git que mencione um endereço deapi.anthropic.comcontendo/git_proxy/. Para saber se a Anthropic atendeu a sessão e corrigir a causa, consulte Quando as sessões não iniciam em um runner com o proxy git. -
Sessions can’t reach the network through an authenticating egress proxy: quando a fonte que você definiu com
--proxy-authorization-commandou--proxy-authorization-filefalha, expira após 30 segundos ou produz um valor vazio, o runner responde essa conexão com502 Bad Gatewaye registra o motivo. O runner redige o stderr do comando nesse log e nunca registra o valor do cabeçalho. Com--proxy-authorization-command, execute o comando você mesmo no host para confirmar que ele imprime o valor do cabeçalho inteiro em stdout. Se o runner sair na inicialização comcould not start the proxy-authorization listener, ele não conseguiu abrir seu listener de loopback. -
O runner registra linhas
Poll failedcontendorejecting the malformed poll response: o runner recebeu uma resposta de work-poll cujo corpo não é o JSON esperado da fila. Uma causa é um proxy interceptador ou um portal cativo respondendo com sua própria página. Se o runner se conecta através de um proxy, configure o proxy para passar as respostas deapi.anthropic.cominalteradas. O runner rejeita a resposta malformada, a contabiliza sob o tipotransportda métricaclaude_code_self_hosted_runner_poll_errors_totale tenta novamente no cronograma de poll com falha descrito em Ciclo de vida da sessão. O runner continua servindo suas sessões ativas. Antes da v2.1.246, o runner lia tal resposta como uma fila de trabalho vazia, o que poderia encerrar suas sessões ativas ou fazer com que ele saísse. -
Uma sessão falha com
no longer exists on the remoteoucouldn't find remote ref: o runner interpreta ocouldn't find remote refdo git como um branch excluído. O branch ainda pode existir. Procure por ele no seu host git, exatamente com o nome que aparece na mensagem, e então verifique cada causa que se aplica:- O branch não existe mais: restaure-o, como a mensagem pede.
- O branch existe e você não administra o runner: envie ao administrador do runner a mensagem completa e informe que o branch existe.
- O branch existe e o runner tem um hook
checkout: um comando git no seu hook pode imprimircouldn't find remote refem stderr antes de o hook sair com um código diferente de zero. Encontre esse comando e corrija a ref que ele solicita ou o remoto do qual ele busca. O runner passa ao seu hook a revisão que a sessão solicitou comoCLAUDE_RUNNER_REPO_REF. - O branch existe e o runner não tem um hook
checkout: o próprio runner busca o branch. Se um--git-host-rewritese aplicar ao repositório, confirme que o host para o qual ele reescreve tem o branch com o mesmo nome e envie o branch para lá se não tiver. - O branch existe e o runner é anterior à v2.1.301: atualize o runner conforme descrito em Fixe a versão, para que uma falha diferente mostre seu próprio erro. Antes da v2.1.301, outras falhas com
couldn't find remote refna saída de erro também eram relatadas como um branch excluído.
-
O Claude Tag às vezes não publica sua resposta no Slack: o lembrete de resposta é um hook que o plano de controle fornece. Confirme que o runner é o Claude Code v2.1.229 ou posterior com
claude --version, já que runners mais antigos ignoram os hooks fornecidos; confirme quepython3 --versioné bem-sucedido dentro da imagem do runner, já que os hooks executampython3; e confirme que nenhum arquivo de configurações desativa os hooks, sejadisableAllHooksemmanaged-settings.json, no~/.claude/settings.jsondo usuário do runner ou no.claude/settings.jsonou.claude/settings.local.jsondo repositório, ouallowManagedHooksOnlyou umstrictPluginOnlyCustomizationque abranja hooks nas configurações gerenciadas. -
Uma sessão inicia sem um de seus repositórios: em um runner sem um hook
checkout, o host git pode recusar a verificação de acesso do runner para um repositório que a sessão apenas lê. O runner então pula esse repositório, registra uma linha[runner:warn] could not access context sourcenomeando a recusa e inicia a sessão nos restantes. O runner pula apenas uma recusa clara: o host responde que o repositório não foi encontrado, git não encontra credenciais para o host, ou a autenticação falha. Uma falha de rede, um timeout ou um HTTP403ainda falha no início da sessão, assim como uma recusa para um repositório para o qual a sessão envia resultados. O runner ainda falha em uma sessão que pular deixaria sem nenhum repositório. Com--use-anthropic-git-proxy, o runner pula apenas um repositório que o proxy git em si nega. A verificação de acesso é executada novamente cada vez que a sessão é iniciada em um runner, portanto, uma vez que a identidade git do runner tenha acesso de leitura, o próximo início clona o repositório. Antes da v2.1.274, cada uma dessas recusas falhava no início da sessão. -
Uma sessão não inicia com
This runner couldn't read: o runner solicitou um repositório ao host git com suas credenciais git e recebeu uma resposta de não encontrado. O repositório não existe, ou essas credenciais não conseguem alcançar o repositório.- Se você administra o runner: conceda às credenciais git dele acesso de leitura ao repositório e, em seguida, inicie uma nova sessão.
- Se você iniciou a sessão: se o repositório não existir, inicie uma nova sessão com o repositório correto. Se o runner não conseguir alcançar o repositório, peça ao administrador do runner que conceda acesso e, em seguida, inicie uma nova sessão.
-
As sessões levam minutos para iniciar ou retomar: uma causa é um clone novo. Em uma frota que inicia cada runner em um disco vazio, sem hook
checkout, o runner clona os repositórios da sessão do zero antes de iniciar o Claude Code. Uma sessão que é retomada em um disco vazio aguarda o mesmo clone novamente. Para evitar o clone novo, forneça um checkout pré-aquecido. Se você já fornece um, confirme que o runner o usa. Onde o próprio runner faz o clone e sai após uma sessão, reduzaCLAUDE_RUNNER_FETCH_DEPTHdo seu padrão de 50 commits para buscar menos histórico. Se você usa um hook, ele é executado no lugar do clone do runner, então verifique o que seu hook faz. Observe a métricaclaude_code_self_hosted_runner_session_init_duration_secondspara ver quanto tempo os inícios levam. -
Os turnos falham com um 401: quando um turno termina com um 401 ou 403 da API da Anthropic, o runner busca um
CLAUDE_CODE_OAUTH_TOKENnovo da Anthropic e o passa para a sessão. O turno com falha não é tentado novamente. Esse token é de curta duração, e o runner o rotaciona pelo stdin da sessão. Quando uma busca falha, o runner registra uma linhainference_token refresh failedque diz quando tentará novamente, e continua tentando novamente enquanto a sessão estiver em execução. Se cada chamada começar a falhar cerca de 30 minutos em uma sessão, um script wrapper provavelmente sevrou o stdin da sessão, então as rotações de token não conseguem alcançá-lo; consulte Keep stdin and file descriptor 3 attached. Antes da v2.1.274, o runner parou de tentar novamente uma busca com falha após algumas tentativas e aguardou a próxima agendada. Uma volta com falha não acionou uma busca, então cada volta falhou com um 401 até a próxima busca agendada. -
Um turno falha em claude.ai/code com
This model needs a newer version of Claude Code: a versão do Claude Code com a qual seus runners iniciam as sessões é mais antiga do que o modelo da sessão exige, então a API rejeita a requisição. Atualize os runners para a versão que a mensagem pede, ou uma posterior, conforme descrito em Fixe a versão. Até lá, as pessoas podem escolher um modelo diferente na sessão. -
Pod is killed mid-drain: aumente
terminationGracePeriodSecondspara pelo menos o valor que o runner registra na inicialização. Consulte Shutdown timing.
[runner:fatal], em stdout, e a saída de depuração em stderr, tudo como linhas de texto simples em vez de JSON. As falhas do diretório base descritas nas entradas de solução de problemas acima são impressas em stderr antes desse ponto. Capture ambos os fluxos com --log-file, que também permite que self-hosted-runner doctor os acompanhe, ou com a coleta de logs da sua plataforma.
O processo filho de cada sessão escreve um log de depuração separado. Em caso de falha, o runner exibe a cauda do log junto com a sessão em claude.ai/code. A menos que você tenha iniciado o runner com --remove-session-state, ele também mantém o log de uma sessão com falha no disco e imprime seu caminho no log do runner.
When the runner exits
Não reinicie um on-demand runner, porque sua ordem de trabalho é de uso único. Um runner que sai logo depois de iniciar precisa de tratamento diferente de um que sai por qualquer outro motivo.- A normal exit: o runner terminou suas sessões e drenagem, atingiu seu tempo de aposentadoria ou foi instruído a parar. Reinicie-o para que o ambiente tenha capacidade novamente. Runner lifecycle descreve essas saídas.
- A failed start: o runner não consegue iniciar com a configuração ou host que foi dado, então sai segundos depois de iniciar, e sai da mesma forma toda vez que você o reinicia. Reiniciá-lo mais rápido não ajuda. Alguém precisa ler sua saída e corrigir a causa.
- Perda de contato: um runner que não consegue alcançar a Anthropic por mais tempo do que seu lease, por exemplo enquanto seu host está em suspensão, pode ser removido do ambiente. Quando um runner removido se reconecta, ele sai. Seu log pode mostrar uma linha
[runner:fatal]que contémrunner record gone server-sideou, após uma interrupção mais longa,poll auth failed. O runner não se registra novamente por conta própria, então reinicie-o.
Recognize a failed start
Quando o runner não consegue iniciar, ele imprime uma linha que diz o motivo e depois sai. Para a maioria das causas, a linha contém[runner:fatal]. Para algumas causas, a linha começa com error: em vez disso, incluindo quando o runner não consegue analisar seus sinalizadores, não consegue ler o segredo do ambiente ou não consegue criar ou escrever no diretório base. A próxima linha então aponta para --help.
A maioria das linhas de log começa com um timestamp e [self-hosted-runner], que a amostra abaixo omite. Por exemplo, um runner iniciado com o proxy git da Anthropic e uma capacidade acima de um imprime uma linha como esta:
--log-file. O runner imprime uma linha error: antes de abrir o arquivo de log, então procure por ela no terminal ou nos logs do seu contêiner, conforme Troubleshooting observa.
Estes também ajudam quando você lê um início com falha:
- No line at all: um runner que o host mata não imprime nenhum. Se a saída terminar sem nenhuma linha
[runner:fatal]e nenhuma linhaerror:, verifique se o host ou seu orquestrador parou o processo, por exemplo por exceder um limite de memória. - The exit code: o runner não reserva um código de saída para erros que se repetem a cada início. Ele sai com o mesmo código para um erro de configuração, como uma combinação não suportada de sinalizadores, e para uma falha que pode se limpar por si mesma, como a API permanecendo inacessível através das próprias tentativas do runner. Baseie a decisão de aguardar mais tempo em quão rápido o runner saiu e leia a saída do runner para aprender por quê.
- An environment that looks healthy: alguns passos de inicialização são executados depois que o runner se registra com seu ambiente, como
--configure-gite a configuração de credenciais do proxy git da Anthropic. Se uma dessas etapas falhar, o ambiente pode continuar listando esse runner por alguns minutos após o processo ter saído, e a página Cloud environments pode ler Healthy enquanto nenhum runner está pegando trabalho. Se as sessões ficarem enfileiradas em um ambiente que parece saudável, verifique se seu supervisor está reiniciando o runner.
Restart with a wait that grows
Como você obtém uma espera crescente depende do seu supervisor.-
Kubernetes: o Deployment nesta página não precisa de alteração. Depois que um contêiner sai, o kubelet por padrão aguarda antes de reiniciar o contêiner, e a espera cresce a cada reinicialização até um teto. A espera recomeça uma vez que o contêiner tenha sido executado por um tempo sem sair.
O kubelet aplica a mesma espera após uma saída normal quando o contêiner foi executado apenas brevemente. Um runner que drena frequentemente pode, portanto, também mostrar o status
CrashLoopBackOff, então leia a saída antes de concluir que o runner não consegue iniciar. O comando abaixo lê a saída da última execução de um pod do Deployment:Quando a última execução foi um início com falha, a linha[runner:fatal]ouerror:está entre as últimas linhas da saída. Para ler a última execução de outro pod, nomeie esse pod no lugar dedeploy/claude-runner. -
Docker and Docker Compose: a Compose recipe nesta página não precisa de alteração. Com
restart: always, o Docker aguarda mais tempo antes de cada reinicialização de um contêiner que continua saindo, até um teto. Substitua<container>pelo nome do contêiner no comando abaixo, que lê quantas vezes o Docker reiniciou o contêiner:O comando imprime um número. Um número que continua subindo significa que o Docker continua reiniciando o runner. -
A systemd unit: por padrão, systemd aguarda o mesmo
RestartSecantes de cada reinicialização e não o alonga, então uma unidade comRestart=alwaysreinicia um runner que não consegue iniciar nesse mesmo intervalo cada vez. Quando os inícios vêm rápido o suficiente para atingir o limite de taxa de início da unidade, cinco inícios em 10 segundos por padrão, systemd para de reiniciar a unidade. A unidade permanece parada até que alguém a inicie novamente, o que systemd permite uma vez que o intervalo do limite de taxa tenha passado ou apóssystemctl reset-failed. ComoRestartSecse aplica a cada reinicialização, um valor mais longo também atrasa a reinicialização após uma saída normal. Escolha um valor que equilibre os dois e alerte sobre a contagem de reinicializações da unidade. - A shell loop or your own supervisor: aplique a mesma regra você mesmo. Comece com uma espera de cinco segundos. Após cada execução que terminou dentro de um minuto, dobre a espera para a próxima reinicialização, até cinco minutos. Após uma execução que durou um minuto ou mais, volte para cinco segundos.
Check why the runner keeps exiting
Quando o runner saiu logo após iniciar várias vezes seguidas, pare e verifique estes antes de reiniciá-lo novamente.- The last
[runner:fatal]orerror:line: ela diz por que o runner parou. Troubleshooting lista as causas comuns. - The combination of flags: o Anthropic git proxy requer
--capacity 1. As receitas nesta página usam uma capacidade mais alta, então baixe-a quando adicionar o proxy a uma delas. - What the service’s environment can reach: se o runner inicia manualmente e falha sob seu supervisor, compare o usuário, o diretório inicial, o
PATHe o limite de memória.--configure-gite o proxy git da Anthropic precisam de git noPATHe um~/.gitconfiggravável. - The environment secret: se você revogou o segredo ou digitou errado, o runner imprime uma linha que contém
RegisterRunner auth failed. - The environment’s Activity tab: abra o ambiente e selecione Activity. Se novos runners continuarem aparecendo lá e nenhum pega trabalho, seu supervisor está reiniciando o runner.
Uma sessão é coletada, mas o Claude nunca responde à primeira mensagem
Se um runner coleta uma sessão, mas o Claude nunca responde à primeira mensagem, e seus runners alcançamapi.anthropic.com através de um proxy, o proxy pode ser a causa. A Anthropic envia a uma sessão suas mensagens por uma única resposta de streaming que permanece aberta. Se seu proxy a armazena em buffer, ou a encerra antes de repassar qualquer coisa, a primeira mensagem nunca chega. Outras requisições da sessão ainda são bem-sucedidas.
Para corrigir, altere como o proxy trata esta requisição:
- Não armazene a resposta em buffer: repasse-a à medida que ela chega. Seu tipo de conteúdo é
text/event-stream. - Deixe a resposta permanecer aberta: ela pode ficar aberta por até uma hora. Se o proxy tiver um timeout de inatividade, mantenha-o bem acima de 15 segundos, porque a Anthropic envia um keep-alive pelo menos com essa frequência por padrão.
Confirme a causa no log do runner
Estas etapas são para runners no Claude Code v2.1.287 ou posterior, no--log-level info padrão. Runners mais antigos registram essas linhas mais tarde, ou não as registram.
- Aguarde
--startup-timeout-minpassar, 15 minutos por padrão. O runner então interrompe a sessão e registra uma linha que contémno child output fore termina com— releasing. - Pesquise no log do runner por
SDKStartup: SSE. Essas linhas vêm depois de uma linhafinished: interrupted, algumas linhas após a da etapa 1. A maioria não tem timestamp, então um coletor de logs pode exibi-las em outro lugar. - Encontre a linha nesta tabela:
Se o log não tiver nenhuma dessas linhas, ou o log do proxy não mostrar nada de errado, relate um problema.
Impeça que a sessão tente novamente
A sessão mantém a mensagem pendente, e um runner que pode atender o proprietário da sessão coleta a sessão novamente. Até que você altere o proxy, a sessão aguarda da mesma forma a cada vez. Para impedir isso, arquive a sessão em claude.ai/code.O que vem a seguir
- Customize sessions: wrapper scripts, lifecycle hooks, on-demand runners, MCP servers, and permissions
- Test end to end: verify a new runner image from CI before promoting it
- Reference: every CLI flag, environment variable, and metric