Skip to main content
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.
Um ambiente auto-hospedado executa sessões na nuvem do Claude Code em runners que você implanta dentro de sua rede, e em produção essas sessões executam código direcionado pelo modelo em nome de todos que podem enviar uma sessão para o ambiente. Esta página é para o operador que leva um ambiente funcionando para produção. Ela funciona através da implantação em ordem: o que bloquear antes de conectar sistemas reais, a saída que a frota precisa, como as sessões se autenticam no seu host git, as receitas de implantação em si, e o que verificar quando as sessões se comportam mal.

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 1 e o padrão --drain-grace-sec 0 para 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-proxy quando 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 login salva para sua conta, ou um GH_TOKEN no ambiente do runner.
  • 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.254 no namespace de rede do contêiner da sessão
    O bloqueio se aplica ao seu script wrapper e hooks de ciclo de vida também, já que compartilham o contêiner. Autentique qualquer troca de token com o JWT da sessão contra seu próprio serviço de token sobre saída na lista de permissões, ou use uma identidade web baseada em arquivo, como IAM Roles for Service Accounts (IRSA) no Amazon EKS.
  • 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-account limita 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ão warn registra uma violação e ainda assim gera a sessão, enforce recusa a sessão, e off desabilita 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 regra Edit, Write, ou NotebookEdit em permissions.allow, ou uma entrada sandbox.filesystem.allowWrite ou allowRead
    • Um bloco env não vazio
    • Uma substituição de postura do operador, como sandbox.enabled: false
    O guard é executado independentemente de --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.
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.ai e platform.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 para api.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 de api.anthropic.com. Consulte Servidores MCP.
Estes fluxos do lado do host alcançam 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.sh de claude.ai no tempo de instalação.
  • claude auth login interativo: faz login através de claude.ai, claude.com e platform.claude.com. A configuração guiada, o modo com login do doctor e 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 de hcaptcha.com, *.hcaptcha.com e challenges.cloudflare.com. Se sua organização usa login único, esse navegador também pode ser redirecionado através de sso.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á inclui Bash, 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çalho Proxy-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 exemplo Bearer <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_PROXY nem HTTP_PROXY contém uma URL http:// ou https://. O runner lê ambas as variáveis em maiúsculas ou minúsculas, e não consulta ALL_PROXY.
  • Qualquer sinalizador passado para o subcomando orquestrador: self-hosted-runner orchestrator nã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çalho Proxy-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_PROXY e HTTP_PROXY você 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 de HTTPS_PROXY ou HTTP_PROXY que você não definiu, e fixa NO_PROXY ao 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-git para 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
Para repositórios em github.com, você também pode iniciar o runner com --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 = Claude e user.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.hooksPath apontando para um diretório de hooks gerenciado pelo runner. Seus hooks commit-msg e prepare-commit-msg adicionam um trailer Co-authored-by: para o criador da sessão a cada commit. O trailer é construído a partir do email em CCR_SESSION_ACCOUNT_EMAIL e omitido quando essa variável não está definida. Se sua imagem já define core.hooksPath e 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].
A assinatura de commit requer git 2.34 ou mais recente; o runner verifica na inicialização e sai com um erro se seu git for mais antigo. Este sinalizador não configura credenciais de push, que você ainda fornece na imagem. Em um runner na v2.1.280 ou posterior, os commits que você faz a partir de um hook de ciclo de vida 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:
Sem uma identidade, 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.helper que retorna um token com escopo mínimo
  • GIT_SSH_COMMAND apontando para uma chave com escopo restrito
Qualquer mecanismo que você configure deve funcionar sem um prompt, porque o clone integrado do runner e fetch desabilitam os prompts que git, SSH, e Git Credential Manager mostrariam de outra forma:
  • 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 seu GIT_SSH_COMMAND se 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 ambiente GIT_ASKPASS em vez disso.
Se seu host git rejeitar a credencial, ou você não configurou uma, o runner tenta novamente algumas vezes e depois falha na preparação do repositório quando o repositório é aquele para o qual a sessão envia resultados. Para um repositório que a sessão apenas lê, Troubleshooting cobre quando o runner o pula em vez disso. O runner não passa essas configurações para o ambiente da sessão. Mantenha qualquer programa que você nomeie em 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-release do runner e qualquer push que seu hook post-session faç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.
Com --use-anthropic-git-proxy definido, o runner exclui e substitui a configuração git global do usuário com o qual é executado, e não mantém nenhum backup. Ele faz isso na inicialização e antes de cada sessão. Um login ou credential helper que você mantinha ali é perdido. As configurações que --configure-git escreve sobrevivem. Execute o runner como um usuário dedicado ou em um contêiner, nunca como seu próprio usuário.
Mantenha configurações git que não são secretas, como identidade e 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.
As receitas Kubernetes e Docker Compose nesta página usam --capacity 4. Se você adicionar --use-anthropic-git-proxy ou CLAUDE_RUNNER_USE_GIT_PROXY=1 a uma delas sem alterar a capacidade para 1, o runner sai na inicialização toda vez que seu orquestrador o reinicia. Defina --capacity 1 e execute mais réplicas para paralelismo. Quando o runner sai mostra a linha que o runner imprime.
Para ativar o proxy git, adicione --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:
Na inicialização, o runner imprime 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-rewrite e --git-ssh-rewrite nã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 ACTIVE nem a linha withheld: 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 contendo remote: 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ém GitHub authentication required. Please reconnect your GitHub account. Peça a essa pessoa que conecte ou reconecte o GitHub nas configurações do claude.ai.
Depois de corrigir a causa, inicie novamente as sessões que falharam.

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 um gh 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 gh integrado 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 gh da sessão. Quando atende, a linha [runner:session] governed git ACTIVE que o runner registra para a sessão mostra gh_path_shim=true. Quando não atende, a sessão não tem gh
  • jq: instale jq na imagem se você quiser que --jq funcione
  • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: se o ambiente da sessão a define, o Claude Code não fornece o gh integrado, e a sessão não tem gh
Quando a imagem inclui o GitHub CLI, as sessões o usam.

Confie em uma autoridade de certificado privada com git gerenciada pela Anthropic

Esta seção se aplica se você definir GIT_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 exemplo GIT_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.
Para conexões git que carregam o token de uma sessão para git gerenciada pela Anthropic, o runner aplica as duas variáveis da seguinte forma. Um hook 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.sslCAInfo nomeando seu arquivo no lugar da variável, mais entradas http.<url>.sslCAInfo que verificam git gerenciada pela Anthropic contra o arquivo por sessão.
    • Hooks checkout e post-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=false no lugar da variável, então as verificações permanecem desligadas para outros hosts. Ele também obtém entradas http.<url>.sslVerify=true que mantêm as verificações ligadas para git gerenciada pela Anthropic.
    • Hooks checkout e post-session: quando a sessão tem um repositório em git gerenciada pela Anthropic, obtêm configuração http.sslVerify=false no lugar da variável. Eles também obtêm entradas http.<url>.sslVerify=true que mantêm as verificações ligadas para git gerenciada pela Anthropic.
O arquivo de certificado por sessão precisa de um pacote de CA do sistema em /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.
Em seguida, inicie uma nova thread no canal e nomeie o repositório em sua primeira mensagem. Se o Claude ainda não conseguir adicionar o repositório, consulte If Claude can’t reach a repository para saber onde verificar o vínculo com o GitHub e a concessão do repositório.

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.com ao comando do runner. O runner então reescreve https://github.com/owner/repo para git@github.com:owner/repo. O SSH deve conectar sem um prompt.
A reescrita de host é executada primeiro, então liste o nome do host interno em --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ário claude, 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:
Troque 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:
A substituição de comando procura o número de lançamento 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.
Em uma especificação de contêiner Kubernetes, defina esses valores iniciais com o seguinte bloco resources:
Builds e testes são geralmente a maior e mais variável parte da carga de uma sessão, então execute um build representativo de seu repositório, meça seu pico de CPU e memória, e aumente qualquer valor inicial que deixe sem espaço para o processo Claude Code no topo desse pico. O runner usa --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 hook spawn-runner submete, como um modelo de pod de um Kubernetes Job.
  • Várias sessões por runner: em um --capacity acima 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 4 sem 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.
    Dê a cada runner seu próprio contêiner. Use o mesmo caminho --base-dir em 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.memory no Kubernetes, --memory no Docker ou mem_limit em 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 MemoryMax na unit do runner e execute um runner por host, porque runners que usam o mesmo --base-dir em um sistema de arquivos compartilham os repositórios clonados ali. Por exemplo, em --capacity 1 sem hook checkout, 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.
Quando um contêiner ou unit atinge seu limite de memória, o kernel mata processos apenas dentro dele. Se você definir --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 retorna EAGAIN, 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, e pids.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.
Não inicie uma sessão ainda, porque você precisa do diretório da próxima etapa antes que o build comece.
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/pids seguido do texto após :pids:
  • A única linha começa com 0::: o host usa cgroup v2, e o diretório é /sys/fs/cgroup seguido do texto após 0::
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: leia pids.current uma 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 --capacity do runner
  • Um limite que vários runners compartilham: multiplique o pico pela soma de suas capacidades
Em seguida, adicione o pico de qualquer outra coisa que compartilhe o limite, como outro contêiner no Pod.
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.max foi atingido: pesquise no log do kernel do host, por exemplo com dmesg, uma linha que contenha cgroup: fork rejected by pids controller in e 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.max no diretório cgroup do runner e em cada diretório acima dele. O menor valor é o limite em vigor, e max significa nenhum limite nesse nível.

Kubernetes

O runner serve GET /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.
O Deployment acima vive em um namespace claude-runners. Crie o namespace primeiro:
Crie o Secret de suporte a partir de um arquivo local contendo o valor que você copiou na etapa Copy environment key da UI de admin, para que o segredo nunca apareça no histórico do shell. Execute (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ós SIGTERM, 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:
  1. O runner aguarda até --drain-wait-sec segundos, 0 por padrão, para que os turnos ainda em andamento sejam concluídos.
  2. 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.
  3. Ele executa o hook de ciclo de vida post-session.
O runner continua sondando a Anthropic durante toda a drenagem. Isso mantém suas sessões atribuídas a ele, para que outro runner não assuma uma delas enquanto seu hook post-session ainda está salvando trabalho não confirmado. O tempo registrado é a soma destes valores: Nas configurações padrão, isso resulta em 0 + 5 + 60 + 15 = 80 segundos. Um --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 exemplo date +%s mais 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: terminationGracePeriodSeconds no Kubernetes, stop_grace_period no Docker Compose. Por exemplo, com --drain-wait-sec 120, substitua o 90 na receita do Kubernetes ou do Docker Compose por 200.
  • 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-min definido, use o número maior na linha de inicialização seguinte, após Size the stop timeout to at least. Tempo de encerramento lista o que aumenta esses números.
Se o timeout de parada se esgotar durante a espera, seu host mata o runner com trabalho ainda em execução. Suas sessões não recebem nenhum hook post-session. O runner também não se desregistra, portanto o plano de controle pode levar alguns minutos, e às vezes mais, para recolocá-las na fila.

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 n minutos: o runner serve suas sessões normalmente e continua aplicando --startup-timeout-min e --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 n minutos 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 n minutos se esgotam e é 75 segundos nos padrões. Se você definir --drain-wait-sec acima de 60 segundos, a carência pós-lançamento será --drain-wait-sec mais 15 segundos.
Em qualquer estágio, o runner sai com 0 assim que não mantém mais sessões. Um segundo sinal encurta os estágios: o runner drena imediatamente, como faz no primeiro sinal sem --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: os n 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 hook post-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 SIGTERM enquanto 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 segundo SIGTERM que o runner recebe. Nada sinaliza um hook post-session em 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 com SIGPIPE, 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ão KillMode=control-group do 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 hook checkout no meio, que permanece anexado ao grupo deliberadamente, mas não um hook post-session em execução no meio ou o filho da sessão.
  • Mortes em nível de cgroup, como o padrão KillMode=control-group do systemd ou o SIGKILL que o Kubernetes entrega para todo o contêiner quando terminationGracePeriodSeconds expira: 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 envia SIGTERM para todo o grupo de processos do hook, depois SIGKILL dois 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ágio SIGTERM está além do alcance do runner.
Quando a drenagem começa, e novamente em uma saída forçada, o runner registra quantos hooks 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 hook checkout, 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, destaca HEAD e redefine duramente para ela, o que é quase instantâneo quando pouco mudou.
  • Em um --capacity acima 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.
Forneça o clone na imagem ou em um volume persistente:
  • 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-dir para 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.
O que o caminho de reutilização faz e não garante:
  • Profundidade do clone: crie o clone sem --depth, com ou sem --single-branch. O runner nunca passa --depth ao buscar em um clone com um remote origin, mesmo que você defina CLAUDE_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 executa git 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 hook checkout enquanto 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-dir persistente, 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-state para 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 reftable perde 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 ref reftable do git perde suas refs dessa forma, então pré-aqueça o clone no formato de ref files do 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-runner inicia. Cada novo runner recebe a nova versão. Um runner que já está em execução, incluindo um runner de reserva que --min-idle iniciou, 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-path ou um hook command para 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=1 no 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 hook spawn-runner para 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, leia claude_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 1800 para 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-min para 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.
Para liberar sessões ociosas, inicie todos os runners com --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.

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 por api.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/*
Se o tráfego de ferramentas deve permanecer dentro de sua rede, execute as ferramentas equivalentes como servidores MCP locais na imagem do runner em vez disso. Consulte Servidores 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.
Uma sessão liberada retoma de um clone fresco, então o trabalho que ela não tinha enviado se foi de qualquer forma; consulte Sessões retomadas perdem trabalho não enviado. Antes da v2.1.260, o runner encerrava cada sessão no limite, após aguardar no máximo a janela de graça para uma vez em execução terminar. Defina o sinalizador acima de sua sessão mais longa esperada, como --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-release em 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 vida checkout não recebem push. Em vez disso, faça um snapshot deles a partir do hook post-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
  • 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 clone via 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 de HEAD para um branch de resgate a partir de um hook post-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 com claude 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.
Problemas comuns:
  • Runner não aparece no ambiente: confirme que o host pode alcançar api.anthropic.com via 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-dir para 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 ser self 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. Defina NODE_EXTRA_CA_CERTS no 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ém GIT_SSL_CAINFO.
  • Seu script de inicialização imprime um erro de escrita contendo Resource temporarily unavailable e sai: seu script compartilha seu stdout ou stderr com o processo claude self-hosted-runner ou 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 sob set -e o 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>&1 ao 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. Defina NODE_EXTRA_CA_CERTS no ambiente do orquestrador para um arquivo PEM com a CA raiz do proxy. Defina-a também para os runners que seu hook spawn-runner inicia. Por exemplo, uma linha pode terminar com poll 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 se connected é true no corpo de /healthz do 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_account metric de cada runner ou o campo locked_account de 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ção act.email, que as sessões de um agente Claude Tag nunca fazem. Sem a reivindicação, o runner não emite nenhuma série locked_account e registra locked_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 com cannot create or write to base directory nesta lista.
  • Uma sessão é coletada, mas o Claude nunca responde à primeira mensagem: se seus runners alcançam api.anthropic.com através de um proxy, configure-o para repassar a resposta /worker/events/stream da 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 por access denied by the git proxy, ou por um erro do git que mencione um endereço de api.anthropic.com contendo /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-command ou --proxy-authorization-file falha, expira após 30 segundos ou produz um valor vazio, o runner responde essa conexão com 502 Bad Gateway e 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 com could not start the proxy-authorization listener, ele não conseguiu abrir seu listener de loopback.
  • O runner registra linhas Poll failed contendo rejecting 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 de api.anthropic.com inalteradas. O runner rejeita a resposta malformada, a contabiliza sob o tipo transport da métrica claude_code_self_hosted_runner_poll_errors_total e 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 remote ou couldn't find remote ref: o runner interpreta o couldn't find remote ref do 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 imprimir couldn't find remote ref em 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 como CLAUDE_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-rewrite se 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 ref na saída de erro também eram relatadas como um branch excluído.
    Depois que o runner for atualizado ou a causa for corrigida, tente novamente a sessão ou inicie uma nova. Se nenhuma dessas verificações encontrar a causa, relate um problema. Para a fonte para a qual a sessão envia resultados, um branch excluído faz a sessão falhar. Para uma fonte git que a sessão apenas lê, o runner pula essa fonte e continua nas restantes. O runner faz a sessão falhar quando pular a deixaria sem nenhum repositório. Antes da v2.1.228, tal sessão começava em um diretório vazio.
  • 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 que python3 --version é bem-sucedido dentro da imagem do runner, já que os hooks executam python3; e confirme que nenhum arquivo de configurações desativa os hooks, seja disableAllHooks em managed-settings.json, no ~/.claude/settings.json do usuário do runner ou no .claude/settings.json ou .claude/settings.local.json do repositório, ou allowManagedHooksOnly ou um strictPluginOnlyCustomization que 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 source nomeando 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 HTTP 403 ainda 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, reduza CLAUDE_RUNNER_FETCH_DEPTH do 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étrica claude_code_self_hosted_runner_session_init_duration_seconds para 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_TOKEN novo 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 linha inference_token refresh failed que 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 terminationGracePeriodSeconds para pelo menos o valor que o runner registra na inicialização. Consulte Shutdown timing.
Depois que o logging é inicializado, o runner escreve seu log de ciclo de vida, incluindo linhas [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ém runner record gone server-side ou, 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.
Configure seu supervisor para reiniciar o runner sempre que sair, para aguardar mais tempo entre reinicializações quando o runner continuar saindo logo após iniciar, e para informar alguém quando isso continuar acontecendo. Para produção, faça com que seu supervisor reinicie o runner em um disco novo, em um novo contêiner ou VM, como recomenda Reforce a segurança da sua implantaçã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:
Procure pela linha na saída padrão e erro padrão do runner, nos logs do contêiner da sua plataforma ou no arquivo que você definiu com --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 linha error:, 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-git e 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] ou error: está entre as últimas linhas da saída. Para ler a última execução de outro pod, nomeie esse pod no lugar de deploy/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 RestartSec antes de cada reinicialização e não o alonga, então uma unidade com Restart=always reinicia 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ós systemctl reset-failed. Como RestartSec se 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] or error: 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 PATH e o limite de memória. --configure-git e o proxy git da Anthropic precisam de git no PATH e um ~/.gitconfig gravá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.
Para diagnóstico orientado no host do runner, execute o subcomando doctor.

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çam api.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.
Em seguida, inicie uma nova sessão e verifique se o Claude responde. Aumentar os timeouts do runner não ajuda: você apenas espera mais antes que o runner interrompa a sessã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.
  1. Aguarde --startup-timeout-min passar, 15 minutos por padrão. O runner então interrompe a sessão e registra uma linha que contém no child output for e termina com — releasing.
  2. Pesquise no log do runner por SDKStartup: SSE. Essas linhas vêm depois de uma linha finished: 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.
  3. 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