Los entornos autohospedados están en beta pública en planes Team y Enterprise; Disponibilidad y limitaciones cubre la ruta de habilitación. Esta página cubre la ejecución de la flota en producción; consulte el inicio rápido para su primer runner y sesión.
Endurezca su implementación
Un runner autohospedado ejecuta código arbitrario dirigido por el modelo en su infraestructura en nombre de todos los que pueden enviar una sesión a su entorno. Eso es cualquier miembro de su organización de Anthropic, y cualquiera que pueda iniciar una sesión de canal de Claude Tag en un alcance que un Propietario enrutó al entorno. Trabaje en cada elemento antes de conectar un entorno a sistemas de producción:-
Contenedores efímeros por sesión: ejecuta cada proceso de runner en un contenedor o VM nuevo que se destruya cuando el proceso salga, con
--capacity 1y el--drain-grace-sec 0predeterminado para que cada contenedor sirva exactamente una sesión. Con una capacidad más alta, o con un drenaje de gracia positivo, un contenedor sirve múltiples sesiones del mismo propietario bloqueado; consulta Ciclo de vida del runner. No reutilices un sistema de archivos entre reinicios de runner, excepto en la configuración deliberada de checkout precalentado, y nunca entre propietarios.- Cuando el runner detiene una sesión, no envía ninguna señal a un proceso que siga en ejecución después de que su comando de shell terminó, como un servicio que se convirtió en daemon. Destruir el contenedor o la VM finaliza ese proceso.
-
Sin credenciales amplias en la imagen: no incluyas claves SSH de larga duración, credenciales de proveedor de nube, o tokens de acceso personal que otorguen más de lo que una sesión necesita. Genera las credenciales utilizadas durante una sesión, como tokens de push o de API, por sesión desde tu script de envoltura. El clon inicial ocurre antes de que se ejecute el script de envoltura, así que gestiónalo con un hook de ciclo de vida
checkout, o con--use-anthropic-git-proxycuando todos los repositorios de una sesión estén en github.com. Para ambos casos, consulta Configurar git. -
Mantén las credenciales de GitHub del host lejos de las sesiones: Claude puede usar cualquier credencial de GitHub que una sesión pueda leer, con el acceso que esa credencial otorgue. Mantén las credenciales de GitHub de alcance amplio del propio host del runner fuera de todo lo que una sesión pueda leer. Una credencial así puede ser un token de acceso personal, el token que
gh auth loginguarda para tu cuenta, o unGH_TOKENen el entorno del runner.- Con git administrado por Anthropic: con una credencial así, Claude llega a GitHub directamente en lugar de hacerlo a través del git administrado por Anthropic.
- Sin git administrado por Anthropic: una credencial de clonación puede permanecer en la imagen si limitas su alcance tan estrictamente como describe Incluir la configuración de git en tu imagen.
- Mantenga el secreto del entorno fuera de los hosts que ejecutan sesiones: el secreto del entorno puede registrar runners y recoger cualquier sesión en cola en el entorno. En una flota fija vive en cada host de runner, donde el código de cualquier sesión puede leer el archivo secreto. Prefiera runners bajo demanda, donde el secreto permanece en el host del orquestador, que nunca ejecuta código de usuario, y cada runner recibe una orden de trabajo de un solo uso que registra exactamente un runner. En una flota fija, trate el archivo de secreto del entorno como legible por cada sesión y rote el secreto después de cualquier compromiso de sesión sospechoso.
- Salida de red de negación predeterminada: restrinja el tráfico saliente del contenedor de runner y sesión en su propio límite de red en cada entorno; Salida de negación predeterminada cubre qué permitir y por qué.
- IAM de host con privilegios mínimos: la identidad de cálculo adjunta al host del runner, como un perfil de instancia o una cuenta de servicio de nodo, debe otorgar solo lo que el runner en sí necesita. Las sesiones deben obtener sus propias credenciales a través de su script de envoltura en lugar de heredar las del host.
-
Bloquee el punto final de metadatos en la nube desde las sesiones: mantener las sesiones fuera de la identidad del host requiere bloquear su acceso al punto final de metadatos en la nube, y las políticas de salida a nivel de subred no interceptan el tráfico de metadatos de enlace local, así que bloquéelo en el contenedor en sí:
- IMDSv2 con un límite de salto de uno
- GKE Workload Identity con ocultamiento de metadatos
- Una denegación explícita para
169.254.169.254en el espacio de nombres de red del contenedor de sesión
-
Aislamiento del sistema de archivos por runner: cada proceso de runner obtiene su propio directorio de trabajo que ningún otro proceso en el host puede leer o escribir. Haga
--hooks-dir, el script de envoltura, y el~/.claude/del host de solo lectura para la sesión, ya sea integrado en la imagen o montado como solo lectura. -
El envío no tiene control de acceso por entorno: cualquier miembro de su organización de Anthropic puede enviar una sesión a cualquiera de sus entornos. Si un Propietario enruta canales de Claude Tag al entorno, cualquiera que la configuración de acceso de Claude Tag admita puede iniciar sesiones de canal que se ejecuten allí. Por defecto, eso es cualquiera en el espacio de trabajo de Slack conectado, con o sin una cuenta de Claude. Trate cada host de runner como alcanzable para la ejecución de código por todos los que pueden enviar al mismo, y coloque en un host de runner solo datos y credenciales que todas esas personas pueden leer.
--lock-to-accountlimita qué sesiones de cuenta ejecuta un host determinado, pero no reduce quién puede enviar al entorno. Para hacer que los entornos autohospedados sean la única opción de selector, un Propietario puede ocultar entornos alojados por Anthropic para toda la organización desde la página Entornos en la nube. -
Aplique la protección de configuración del repositorio: elija el modo de protección con
--confine-repo-settings. Elwarnpredeterminado registra una violación y aún genera la sesión,enforcerechaza la sesión, yoffdesactiva el escaneo. El runner escanea la configuración comprometida de cada repositorio para:- Una concesión que se resuelve fuera del espacio de trabajo de esa sesión: una entrada
additionalDirectories, una reglaEdit,Write, oNotebookEditenpermissions.allow, o una entradasandbox.filesystem.allowWriteoallowRead - Un bloque
envno vacío - Una anulación de postura del operador como
sandbox.enabled: false
--trust-workspace, y no cubre hooks de repositorio,.mcp.json, o reglas de Bash; consulte Permisos y aprobación de herramientas para saber dónde pertenecen esas concesiones. - Una concesión que se resuelve fuera del espacio de trabajo de esa sesión: una entrada
Si tu organización tiene habilitada la lista de IP permitidas, agrega las direcciones públicas de salida de tus runners y contenedores de sesión a la lista de permitidos antes de iniciarlos. Si ejecutas runners bajo demanda, agrega también la dirección del host del orquestador. No confíes en la lista de permitidos como control de red para el tráfico de runners o sesiones. En su lugar, aplica salida con denegación predeterminada en tu propio límite de red.
Requisitos de red
El runner y los hijos de sesión que genera hacen conexiones salientes a los hosts a continuación. Restrinja la salida del contenedor de sesión a estos hosts y los servicios internos específicos que las sesiones necesitan alcanzar; Salida de negación predeterminada cubre cómo y por qué. Estos hosts siempre son requeridos:
Un runner que usa
--use-anthropic-git-proxy enruta su tráfico de git de github.com a través de api.anthropic.com, por lo que no necesita la ruta al host de git para github.com. Aún necesita esa ruta si estableces --push-outcome-on-release o haces push desde un hook post-session.
Si estos hosts son necesarios depende de su configuración:
No necesitas agregar estos hosts a la lista de permitidos para el tráfico del runner o de las sesiones:
statsig.anthropic.com,*.sentry.io,claude.aiyplatform.claude.com: estos hosts aparecen en algunas listas de verificación de red empresarial más antiguas, pero el runner no los alcanza. Las obtenciones de banderas de características van aapi.anthropic.com, y el runner se autentica con el secreto del entorno en lugar de OAuth interactivo.mcp-proxy.anthropic.com: las sesiones autohospedadas no lo usan. Cuando la entrega de conectores está habilitada para tu organización, los conectores de claude.ai de tu organización llegan a las sesiones a través deapi.anthropic.com. Consulta Servidores MCP.
claude.ai, así que ejecútalos desde un host cuya salida lo permita en lugar de ampliar la salida del contenedor de sesión:
- El instalador de una línea: obtiene
install.shdeclaude.aien el momento de la instalación. claude auth logininteractivo: inicia sesión a través declaude.ai,claude.comyplatform.claude.com. La configuración guiada, el modo con sesión iniciada dedoctory el envío desde CI lo usan. El navegador con el que inicias sesión también carga las verificaciones de navegador de la página de inicio de sesión de claude.ai desdehcaptcha.com,*.hcaptcha.comychallenges.cloudflare.com. Si tu organización usa inicio de sesión único, ese navegador también puede ser redirigido a través desso.claude.com.
Salida de negación predeterminada
Implemente contenedores de runner y sesión en un segmento de red o espacio de nombres cuyo tráfico saliente se limita a los hosts en la tabla de requisitos de red, su host de git, y los servicios internos específicos que las sesiones necesitan alcanzar. El producto no puede verificar o aplicar esto, así que aplíquelo en su propio límite de red en cada entorno. El código de sesión está dirigido por el modelo e intenta conexiones a hosts arbitrarios; la salida de negación predeterminada a nivel de red limita dónde esos intentos pueden aterrizar. Esto se aplica independientemente del modo de permiso: el conjunto de herramientas preaprobadas predeterminadas ya incluyeBash, así que la salida de shell se ejecuta sin un aviso incluso sin modo automático.
Para detalles sobre qué telemetría emite cada sesión y cómo desactivarla, consulte Telemetría.
Autentíquese en un proxy de salida
Algunos proxies de salida corporativos requieren un encabezadoProxy-Authorization en cada conexión. El token en ese encabezado a menudo rota demasiado rápido para escribir en la URL del proxy que establece en HTTPS_PROXY. Establezca HTTPS_PROXY o HTTP_PROXY en la URL de su proxy como de costumbre, luego establezca --proxy-authorization-command o --proxy-authorization-file para indicar al runner dónde leer el valor del encabezado. Ambas banderas requieren Claude Code v2.1.238 o posterior.
Elija de dónde viene el valor Proxy-Authorization
Elija la bandera que coincida con cómo produce el token Proxy-Authorization:
--proxy-authorization-command <command>: elija esto para un token que genera bajo demanda. El runner ejecuta el comando de shell y usa su stdout recortado como el valor del encabezado, por ejemploBearer <token>.--proxy-authorization-file <path>: elija esto para un token que otro proceso rota en su lugar. El runner lee el archivo y usa su contenido recortado como el valor del encabezado.
Configuraciones que el runner se niega a iniciar con
Cada bandera también tiene una forma de variable de entorno, listada junto a ella en la referencia de banderas CLI del runner. Antes de que el runner contacte su proxy o el plano de control, verifica las banderas y sus variables, y se niega a iniciar en tres casos:- Ambas banderas establecidas: una bandera más la variable de entorno de la otra bandera cuenta como establecer ambas.
- Sin URL de proxy: ni
HTTPS_PROXYniHTTP_PROXYcontiene una URLhttp://ohttps://. El runner lee ambas variables en mayúsculas o minúsculas, y no consultaALL_PROXY. - Cualquiera de las banderas pasadas al subcomando del orquestador:
self-hosted-runner orchestratorno acepta las banderas o sus variables de entorno. Pase la bandera a cada runner que inicia el orquestador en su lugar.
Lo que el runner cambia mientras se establece una bandera de autorización de proxy
Con cualquiera de las banderas establecidas, el runner inicia un oyente propio y envía tráfico de proxy desde sí mismo, sus hooks de ciclo de vida, y sus sesiones a través de ese oyente. El oyente agrega el encabezadoProxy-Authorization en el camino a su proxy.
- Oyente: el oyente es un proxy directo en
127.0.0.1. El runner inicia el oyente antes de registrarse con el plano de control, y sale al inicio si el oyente no puede iniciar. - Variables de proxy: el runner reescribe cualquiera de
HTTPS_PROXYeHTTP_PROXYque establezca para que apunte al oyente. Ese valor reescrito alcanza el runner en sí, sus hooks de ciclo de vida, y cada sesión que ejecuta. - Rotación de token: un token rotado entra en vigor sin un reinicio. Para cada conexión que el oyente abre a su proxy, el runner ejecuta su comando o lee su archivo nuevamente y agrega el resultado como el encabezado.
- Entorno de sesión: una sesión alcanza su proxy solo a través del oyente. En el entorno de cada sesión, el runner elimina
ALL_PROXY, elimina cualquier ortografía deHTTPS_PROXYoHTTP_PROXYque no haya establecido, y fijaNO_PROXYal valor propio del runner. - Registros: el runner nunca registra el valor del encabezado.
Configurar git
El runner gestiona checkouts de repositorio pero no configura la identidad de git o credenciales por defecto. Usted controla la imagen y el entorno de proceso del runner, así que controla la configuración de git. Elija uno de dos enfoques:- Deje que el runner configure git: inicie el runner con
--configure-gitpara que escriba la misma identidad y configuración de firma de commit que usan las sesiones alojadas por Anthropic - Envíe la configuración de git en su imagen: establezca la identidad y las credenciales de push usted mismo, por ejemplo para hacer commits bajo su propia identidad de bot
--use-anthropic-git-proxy, o establecer CLAUDE_RUNNER_USE_GIT_PROXY=1, para pedirle a Anthropic que atienda git para las sesiones del runner.
Pisos de versión de Git en el host del runner: --configure-git la firma de commit SSH requiere Git 2.34 o más reciente, --use-anthropic-git-proxy requiere 2.32 o más reciente, y reanudar sesiones desde ramas empujadas por --push-outcome-on-release requiere 2.29 o más reciente. Git 2.24 es suficiente si omite los tres y gestiona la identidad de git usted mismo.
Deje que el runner configure git
Inicie el runner con--configure-git, o establezca SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, para que escriba la configuración global de git al inicio:
user.name = Claudeyuser.email = noreply@anthropic.com, coincidiendo con sesiones alojadas por Anthropic- Firma de commit y etiqueta en formato SSH, enrutada a través de un shim gestionado por el runner que firma cada commit a través del servicio de firma de Anthropic usando las credenciales de la sesión. Las firmas son verificables en GitHub contra la clave de firma SSH publicada de Anthropic.
push.negotiate = true, para que git pregunte a su host de git qué commits ya tiene antes de empacar un push. Requiere Claude Code v2.1.257 o posterior.core.hooksPathapuntando a un directorio de hooks gestionado por el runner. Sus hookscommit-msgyprepare-commit-msgañaden a cada commit un tráilerCo-authored-by:para el creador de la sesión. El tráiler se construye a partir del correo electrónico enCCR_SESSION_ACCOUNT_EMAILy se omite cuando esa variable no está establecida. Si tu imagen ya establececore.hooksPathy el runner no usa git gestionado por Anthropic, el runner conserva tu ajuste, omite instalar estos hooks e imprime una advertencia[runner:git].
checkout o post-session también se firman como la sesión, sin el tráiler Co-authored-by:. Configuración de git dentro de los hooks de ciclo de vida describe los ajustes de git que el runner fija dentro de esos hooks.
Con o sin --configure-git, Claude Code le indica a Claude que termine sus mensajes de commit con un tráiler Claude-Session: <url> y sus descripciones de pull request con la URL de la sesión. Para omitir ambos, establece attribution.sessionUrl en false en el ~/.claude/settings.json del host del runner y luego reinicia el runner.
Envíe la configuración de git en su imagen
La identidad de Git es requerida para cualquier commit. Establézcala a nivel del sistema en su Dockerfile para que la configuración se aplique independientemente de qué usuario ejecute el proceso del runner:git commit falla con Please tell me who you are y las sesiones no pueden progresar. Puede usar su propia identidad de bot en su lugar; el runner no anula estos valores.
No incluyas credenciales de push de larga duración o de alcance amplio en una imagen de runner compartida: una credencial en la imagen está disponible para cada sesión que ejecuta la imagen, sin importar quién la haya iniciado. En su lugar, genera un token de corta duración y alcance mínimo por sesión desde tu script de envoltura, usando la identidad del creador de la sesión decodificada del JWT de la sesión. Combínalo con un contenedor efímero por sesión, que requiere --capacity 1, para que ninguna credencial sobreviva a la sesión que la generó; consulta la sección de endurecimiento.
Si debe configurar credenciales de push a nivel de imagen, por ejemplo para una clave de implementación de solo lectura, limítelas lo más posible que su host de git permita:
- Una clave de implementación SSH limitada a un repositorio con una reescritura
url.<base>.insteadOf - Un
credential.helperque devuelve un token de alcance mínimo GIT_SSH_COMMANDapuntando a una clave de alcance estrecho
- El runner establece
GIT_TERMINAL_PROMPT=0, por lo que git no pide un nombre de usuario o contraseña. - El runner ejecuta SSH con
BatchMode=yes, añadido a suGIT_SSH_COMMANDsi establece uno, por lo que SSH no pide una frase de contraseña o confirmación de host. - El runner establece
GCM_INTERACTIVE=never, por lo que Git Credential Manager no abre un diálogo de inicio de sesión. - El runner borra
core.askPass, así que si usa un ayudante askpass, establézcalo a través de la variable de entornoGIT_ASKPASSen su lugar.
GIT_SSH_COMMAND o GIT_ASKPASS donde las sesiones no puedan escribir en él, de la manera que la lista de verificación de endurecimiento pide para el directorio de hooks y el script de envoltura. Lo mismo aplica para cualquier clave o archivo en la línea de comando de ese programa. El propio git del runner ejecuta ese programa cuando clona u obtiene.
Si los directorios de checkout son propiedad de un uid diferente al del proceso del runner, git se niega a operar en ellos; agregue safe.directory:
Use el proxy de git de Anthropic
Con el proxy de git de Anthropic, también llamado git gestionado por Anthropic, la imagen del runner no necesita claves SSH, ayudante de credenciales,.netrc ni otras credenciales de git para la propia sesión. En su lugar, el runner le pide a Anthropic que atienda git para sus sesiones. Para una sesión de usuario que Anthropic atiende, el clon del runner y los propios fetch y push de la sesión pasan por Anthropic, que usa el token OAuth de GitHub almacenado para el creador de la sesión. Cómo atiende Anthropic git para una sesión cubre las sesiones de bot y de agente.
El proxy de git está desactivado a menos que lo actives. Un runner que llega a tu host de git con sus propias credenciales no lo necesita, y su git funciona con cualquier host de git.
A cambio, el proxy de git limita lo que el runner admite y cambia lo que necesita:
- Solo github.com: Anthropic atiende una sesión solo cuando todos sus repositorios están en github.com, y el proxy de git aún no admite GitHub Enterprise Server. En un runner con el proxy de git, una sesión con un repositorio en otro host de git no se inicia.
- Credenciales solo para los repositorios de la sesión: Anthropic proporciona credenciales de git para los repositorios que forman parte de la sesión, no para otros repositorios en el mismo host de git. Un submódulo privado, una dependencia que tu gestor de paquetes obtiene con git o un marketplace de plugins en otro repositorio no reciben ninguna credencial de Anthropic. Pide a las personas que crean sesiones que agreguen cada repositorio que una sesión necesita al crearla. Cada ejecución de una tarea programada en la nube es una sesión con los repositorios de la tarea, así que pide al propietario de cada tarea que agregue cada repositorio que necesitan sus ejecuciones.
- Solo push de ramas: un push que elimina una rama falla, y también falla un push a cualquier otro tipo de ref, como una etiqueta. Para saber qué ramas puede actualizar un push, consulta Proxy de GitHub.
- Cuentas de GitHub conectadas: pide a cada persona que crea sesiones de usuario con un repositorio que conecte GitHub. Sin su propia conexión, esas sesiones no se inician.
-
--capacity 1: el proxy de git requiere una sesión por proceso de runner, así que ejecuta más réplicas para paralelismo. Activa el proxy de git de Anthropic enumera los requisitos. - Configuración global de git reemplazada: el runner elimina y reemplaza la configuración global de git del usuario con el que se ejecuta. Ejecútalo como un usuario dedicado o en un contenedor.
-
Credenciales del host para los push del host: el push de
--push-outcome-on-releasedel runner y cualquier push que haga tu hookpost-sessionsiguen usando las propias credenciales de git del host del runner y su ruta de red haciagithub.com. Mantén esas credenciales fuera de la configuración global de git, y consulta Incluye la configuración de git en tu imagen para conocer formas de proporcionarlas. - Decisión por sesión: Anthropic decide para cada sesión del runner si atiende su git, y una sesión que no atiende no se inicia. Cuando las sesiones no se inician en un runner con el proxy de git cubre las causas.
safe.directory, en la configuración de git del sistema.
Activa el proxy de git de Anthropic
Antes de iniciar el runner con--use-anthropic-git-proxy, confirma que el host del runner cumple cada uno de estos requisitos. El runner se niega a iniciar cuando no se cumple el requisito de capacidad o de git:
- Claude Code v2.1.267 o posterior: las versiones anteriores aceptan el flag pero no reportan la solicitud para que Anthropic atienda git ni imprimen la línea
Registering as opted in, por lo que Anthropic no atiende sus sesiones. --capacity 1, el valor predeterminado: cada proceso de runner maneja una sesión a la vez, así que ejecuta más réplicas para paralelismo.- Git 2.32 o posterior: las versiones anteriores de git ignoran la configuración de git por sesión que el runner prepara para el proxy de git.
--use-anthropic-git-proxy al comando del runner, o establece CLAUDE_RUNNER_USE_GIT_PROXY=1 en el entorno del runner. Este comando, ejecutado en un shell en el host del runner, inicia el runner del inicio rápido con el proxy de git activado:
Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). Luego, Anthropic decide para cada sesión en ese runner si atiende su git. Para cada sesión que atiende, el runner registra una línea [runner:session] que contiene governed git ACTIVE. Si en cambio una sesión no se inicia, consulta Cuando las sesiones no se inician en un runner con el proxy de git.
Cómo atiende Anthropic git para una sesión
Para una sesión que Anthropic atiende, el clon del runner y los propios fetch y push de la sesión pasan por Anthropic, autenticados con el token de corta duración de la propia sesión:- Sesiones de usuario: Anthropic usa el token OAuth de GitHub almacenado para el creador de la sesión.
- Sesiones de bot y de agente: Anthropic usa el token de instalación de GitHub App de tu organización.
- Reescrituras de URL:
--git-host-rewritey--git-ssh-rewriteno tienen efecto en un repositorio que atiende el proxy de git.
Cuando las sesiones no se inician en un runner con el proxy de git
En un runner iniciado con--use-anthropic-git-proxy, una sesión no se inicia cuando Anthropic no atiende su git. Busca en el registro del runner un error de git que nombre una dirección de api.anthropic.com que contenga /git_proxy/.
Para cada sesión, un runner con Claude Code v2.1.267 o posterior también registra una línea [runner:session] que contiene governed git ACTIVE cuando Anthropic atiende el git de la sesión, o una línea [runner:warn] que contiene the server withheld Anthropic-managed git for this session cuando no lo hace. Busca la línea que ves entre estos casos:
- Ni
governed git ACTIVEni la líneawithheld: un runner anterior a Claude Code v2.1.267 no registra ninguna de las dos líneas, y Anthropic no atiende sus sesiones. Actualiza el runner a v2.1.267 o posterior siguiendo Fija la versión. - La línea
withheld: Anthropic no atendió la sesión. Un runner que antes funcionaba con el proxy de git puede fallar así sin ningún cambio de tu parte.- Un repositorio no está en github.com: una sesión con aunque sea un repositorio en otro host de git, como GitHub Enterprise Server, no se atiende, incluidos sus repositorios de github.com. Desactiva el proxy de git de Anthropic para los runners de ese entorno.
- Todos los repositorios están en github.com: reporta el fallo a tu equipo de cuenta de Anthropic con el ID de sesión de la línea
withheld. Anthropic registra el motivo de su lado.
- Una línea que contiene
remote: access denied by the git proxy: una sesión que Anthropic atiende aún puede ser rechazada, por ejemplo cuando la política de la organización deniega el acceso a git para la sesión, o la sesión no está autorizada para el repositorio. El registro del runner muestra entonces una línea que contieneremote: access denied by the git proxy, y el resto de esa línea indica el motivo. GitHub authentication required: esto aparece cuando el creador de la sesión no tiene una conexión de GitHub funcional en claude.ai. El clon de la sesión falla, y el error de git diceGitHub authentication required. Please reconnect your GitHub account.Pídele a esa persona que conecte o vuelva a conectar GitHub en su configuración de claude.ai.
Desactiva el proxy de git de Anthropic
Si las sesiones de un entorno usan un repositorio en un host de git distinto de github.com, como GitHub Enterprise Server, desactiva--use-anthropic-git-proxy para los runners de ese entorno.
1
Quita el flag
Quita
--use-anthropic-git-proxy del comando del runner. Si estableciste CLAUDE_RUNNER_USE_GIT_PROXY en el entorno del runner, como en una especificación de pod o un archivo de Compose, quítala de ahí. En un shell, elimínala:2
Dale credenciales de git al runner
Proporciona credenciales que funcionen sin pedir nada para cada host de git que usan las sesiones de los runners, github.com incluido. Cualquier credencial que estuviera en la configuración global de git del usuario del runner ya no existe, porque el runner eliminó esa configuración mientras
--use-anthropic-git-proxy estaba establecido. Incluye las credenciales en tu imagen o usa un hook de ciclo de vida checkout.3
Abre la ruta de red
Permite que el runner llegue a cada host de git que usan las sesiones de los runners en el puerto 443 o 22. Consulta la fila del host de git en Requisitos de red.
4
Reinicia los runners
Reinicia los runners para que se registren sin el proxy de git. Luego vuelve a iniciar cada sesión fallida.
Acceso a la API de GitHub sin la GitHub CLI
Si la imagen de tu runner no incluye la GitHub CLI, Claude Code puede proporcionar ungh integrado, para que Claude pueda seguir abriendo pull requests, comentando y leyendo los resultados de CI. El gh integrado es para runners que usan git gestionado por Anthropic. Admite un solo comando, gh api, que llama a la API REST de GitHub. Requiere Claude Code v2.1.287 o posterior en la imagen del runner.
Este comando abre un pull request en lugar de gh pr create. El gh integrado completa {owner} y {repo} para el repositorio actual:
- Credenciales: el
ghintegrado envía sus solicitudes REST a través de git gestionado por Anthropic, que proporciona la credencial de GitHub del lado de Anthropic, así que la imagen no necesita ningún token de GitHub para ello - Qué sesiones lo obtienen: Anthropic decide por sesión si git gestionado por Anthropic atiende el
ghde la sesión. Cuando lo hace, la línea[runner:session] governed git ACTIVEque el runner registra para la sesión muestragh_path_shim=true. Cuando no lo hace, la sesión no tienegh jq: instalajqen la imagen si quieres que--jqfuncioneCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: si el entorno de la sesión la establece, Claude Code no proporciona elghintegrado, y la sesión no tienegh
Confíe en una autoridad de certificados privada con git gestionado por Anthropic
Esta sección aplica si estableceGIT_SSL_CAINFO o GIT_SSL_NO_VERIFY en el entorno de un runner cuyas sesiones usan git gestionado por Anthropic. El manejo que describe requiere que el runner ejecute Claude Code v2.1.283 o posterior.
Cuando git en el runner debe confiar en una autoridad de certificados privada (CA), como la que firma un proxy de inspección TLS, los enfoques habituales funcionan de la siguiente manera:
- Almacén de certificados del sistema: instale su CA en el almacén de certificados del sistema del host del runner, y git confía en él sin ninguna variable.
GIT_SSL_CAINFO: establézcalo en un archivo PEM de sus CAs, por ejemploGIT_SSL_CAINFO=/etc/ssl/corp-ca.pem.GIT_SSL_NO_VERIFY: no ayuda detrás de un proxy de re-firma. El clon propio del runner a través de git gestionado por Anthropic verifica certificados incluso cuando la variable está establecida, así que ese clon falla hasta que git confía en su CA a través de uno de los otros dos enfoques.
command comienza con el entorno de la sesión, así que obtiene lo que git dentro de la sesión obtiene:
GIT_SSL_CAINFO: contra qué verifica git a git gestionado por Anthropic depende de dónde se ejecute git:- Clon y fetch propios del runner: se ejecutan sin la variable y verifican git gestionado por Anthropic contra un archivo de certificado por sesión que escribe el runner. El runner crea ese archivo a partir del paquete de CA del sistema del host del runner y los certificados de tu archivo.
- Git dentro de la sesión: obtiene configuración
http.sslCAInfoque nombra tu archivo en lugar de la variable, más entradashttp.<url>.sslCAInfoque verifican git gestionado por Anthropic contra el archivo por sesión. - Hooks
checkoutypost-session: heredan la variable sin cambios.
GIT_SSL_NO_VERIFY: qué verificaciones de certificado permanecen desactivadas depende de dónde se ejecute git:- Clon y obtenciones propios del runner: se ejecutan sin la variable y verifican el certificado que se les presenta.
- Git dentro de la sesión: obtiene configuración
http.sslVerify=falseen lugar de la variable, así que las verificaciones permanecen desactivadas para otros hosts. También obtiene entradashttp.<url>.sslVerify=trueque mantienen las verificaciones activadas para git gestionado por Anthropic. - Hooks
checkoutypost-session: cuando la sesión tiene un repositorio en git gestionado por Anthropic, obtienen configuraciónhttp.sslVerify=falseen lugar de la variable. También obtienen entradashttp.<url>.sslVerify=trueque mantienen las verificaciones activadas para git gestionado por Anthropic.
/etc/ssl/certs/ca-certificates.crt o /etc/pki/tls/certs/ca-bundle.crt en el host del runner. También necesita un archivo GIT_SSL_CAINFO que el usuario del runner pueda leer, que contenga bloques CERTIFICATE PEM, y que tenga como máximo 1 MiB. Cuando el runner no puede construir el archivo por sesión, registra una línea [runner:warn] que contiene did not build the certificate file y la razón. Git luego usa su archivo tal como es para git gestionado por Anthropic. Corrija lo que la línea nombra.
Para cada sesión que usa git gestionado por Anthropic, el runner también registra una línea [runner:warn] que comienza con governed git: GIT_SSL_CAINFO is set o governed git: GIT_SSL_NO_VERIFY is set. La línea dice qué hizo el runner con esa variable para su propio git, para git dentro de la sesión, y para sus hooks de ciclo de vida. Termina con si necesita cambiar algo.
Agrega repositorios de github.com a sesiones de canales de Claude Tag
Si Claude no puede agregar un repositorio de github.com a una sesión de un canal de Claude Tag, asegúrate de que el canal tenga acceso a él, incluso cuando tu runner pueda clonarlo. Ese acceso proviene de ajustes en GitHub y en tu organización de Claude, no de las credenciales de git de tu runner.1
Instala la Claude GitHub App
Instala la Claude GitHub App en la organización o cuenta de GitHub propietaria del repositorio, e incluye ese repositorio en la instalación.
2
Vincula la organización o cuenta de GitHub
En Organization settings > Git providers, vincula esa organización o cuenta a tu organización de Claude. Un Owner de tu organización de Claude que también sea propietario de la organización de GitHub puede vincularla. Consulta Vincula tu organización de GitHub.
3
Concede el repositorio
En la configuración de administración de Claude Tag, concede el repositorio al canal o a un alcance más amplio que incluya el canal: su espacio de trabajo de Slack o todo Slack. Alguien con el rol Owner o el permiso Claude Tag Admin puede concederlo. Un administrador del canal también puede agregarlo a su propio canal si su cuenta de GitHub es administradora del repositorio. Consulta Concede acceso al repositorio.
Reescriba URLs de git para redes privadas
Las URLs de repositorio llegan desde el plano de control como HTTPS, con el nombre de host de tu host de git; para GitHub Enterprise, es el nombre de host que configuraste para la integración de GitHub Enterprise en claude.ai. Dos flags repetibles reescriben esas URLs antes del clon:--git-host-rewrite <from>=<to>: para DNS de horizonte dividido, donde Anthropic alcanza su host de git a través de un nombre de host externo pero los runners deben usar uno interno--git-ssh-rewrite <host>: para hosts de git que solo aceptan SSH, o un host de runner que solo tiene una clave SSH. Por ejemplo, para clonar repositorios de github.com por SSH, agrega--git-ssh-rewrite github.comal comando del runner. El runner entonces reescribehttps://github.com/owner/repoagit@github.com:owner/repo. SSH debe conectarse sin pedir nada.
--git-ssh-rewrite si necesita ambos. Para control total sobre el checkout, use un hook de ciclo de vida checkout.
Construya la imagen del runner
Anthropic no publica una imagen de runner precompilada. Construya la suya alrededor del binarioclaude, agregando cualquier cadena de herramientas que sus repositorios necesiten: tiempos de ejecución de lenguaje, compiladores, gestores de paquetes, y sidecars de MCP.
Las recetas a continuación usan --capacity 4, por lo que un contenedor sirve hasta cuatro sesiones concurrentes del mismo propietario bloqueado. Eso no proporciona el aislamiento del contenedor por sesión en la sección de endurecimiento: antes de conectar un entorno a sistemas de producción, ejecute las recetas en --capacity 1 con un contenedor por sesión, o use runners bajo demanda, que también mantienen el secreto del entorno fuera de los hosts que ejecutan sesiones. Si agrega el proxy de git de Anthropic a una de estas recetas, también cambie --capacity a 1.
Este Dockerfile es un punto de partida mínimo. Instala python3 porque los hooks que el plano de control de Anthropic proporciona a las sesiones de Claude Tag, incluido el que hace que Claude publique su respuesta en Slack, ejecutan python3. Mantén python3 en el PATH en cualquier imagen que sirva sesiones de Claude Tag:
linux-x64 por linux-arm64 si tus nodos son ARM, o por linux-x64-musl o linux-arm64-musl en una imagen basada en musl como Alpine; consulta Configuración de Alpine Linux para los paquetes adicionales que las imágenes musl necesitan. La URL es la ubicación de lanzamiento estándar de Claude Code, por lo que puedes verificar el binario descargado contra el manifiesto firmado del lanzamiento como se describe en Integridad binaria y firma de código. El runner requiere la versión 2.1.224 de Claude Code o posterior, o la 2.1.229 o posterior para servir sesiones de Claude Tag. Crea la imagen, luego súbela a tu registro y haz referencia a ella en las recetas a continuación:
stable actual y lo pasa como argumento de compilación, por lo que ejecutar el mismo comando después de un nuevo lanzamiento estable reconstruye la capa de descarga con el binario más nuevo. Para fijar un lanzamiento específico para compilaciones reproducibles, pase el número de versión directamente como CLAUDE_CODE_VERSION. Reemplace stable con latest en la URL de búsqueda cuando necesite un lanzamiento más nuevo que el canal estable, como uno que un modelo recién lanzado requiere.
Dimensione CPU y memoria para sesiones
Dimensione el contenedor o host de un runner para las sesiones que ejecuta en lugar de para el proceso del runner. El runner en sí sondea trabajo, prepara el checkout de cada sesión, ejecuta sus hooks de ciclo de vida, e inicia y supervisa los procesos de sesión. La carga proviene de las sesiones: cada una es un proceso de Claude Code más lo que inicia, como compilaciones, suites de prueba, instalaciones de paquetes, y servidores MCP. Para una sesión, comience con los siguientes valores, indicados como solicitudes y límites de Kubernetes o el equivalente de su plataforma, y trate los como un punto de partida en lugar de un requisito:- Memoria: una solicitud y un límite de 4 GiB cada uno, que cumple con el mínimo de 4 GB en los requisitos del sistema de Claude Code. Mantenga los dos iguales para que el programador tenga en cuenta la memoria completa del contenedor. Cuando el contenedor alcanza su límite de memoria, el kernel mata procesos dentro de él, lo que puede terminar una sesión a mitad de la tarea.
- CPU: una solicitud de 2 CPUs y un límite de 4 CPUs, para que una sesión pueda aumentar por encima de la solicitud durante compilaciones. El kernel acelera un contenedor en su límite de CPU en lugar de matar procesos en él, por lo que las sesiones en el límite se ejecutan más lentamente pero siguen ejecutándose.
resources:
--capacity para limitar cuántas sesiones ejecuta a la vez. No divide CPU ni memoria entre ellas, por lo que las sesiones en un runner comparten la CPU y la memoria del contenedor. Para evitar que el uso de memoria de una sesión termine las demás, ejecuta una sesión por runner con su propio límite de memoria.
-
Una sesión por runner: dé a cada contenedor los valores de una sesión. Use este dimensionamiento en
--capacity 1, que la sección de endurecimiento recomienda, y para runners bajo demanda, donde establece los valores en la carga de trabajo que su hookspawn-runnerenvía, como la plantilla de pod de un Job de Kubernetes. -
Varias sesiones por runner: en un
--capacitypor encima de uno, multiplique los valores de una sesión por la capacidad, porque hasta esa cantidad de sesiones pueden ejecutarse en el contenedor a la vez. Las recetas de Kubernetes y Docker Compose ejecutan--capacity 4sin límites de CPU o memoria, así que agregue límites dimensionados para la capacidad que ejecuta. -
Varios contenedores de runner en un host: dimensiona el host para la suma de las capacidades de los runners, porque cada runner cuenta solo sus propias sesiones contra
--capacity:- Memoria: multiplica la memoria de una sesión por la suma y luego agrega lo que usan los propios procesos del host
- CPU: multiplica la solicitud de CPU de una sesión por la suma. Para permitir que todas las sesiones superen su solicitud a la vez, multiplica en su lugar su límite de CPU.
--base-diren cada uno, en un almacenamiento que ningún otro contenedor de runner monte, para que cada runner tenga su propio directorio de trabajo.
Da a cada sesión su propio límite de memoria
Para evitar que una sesión termine las demás cuando se agota la memoria, ejecuta cada runner en--capacity 1 bajo su propio límite de memoria, establecido en la memoria de una sesión:
- En un contenedor: ejecuta un runner por contenedor y establece el límite de memoria del contenedor, como
limits.memoryen Kubernetes,--memoryen Docker omem_limiten un archivo de Compose. Mantén la suma de los límites de los contenedores por debajo de la memoria del host, porque cuando el propio host se queda sin memoria, el kernel puede matar procesos en cualquier parte del host. - Bajo systemd sin contenedor: establece
MemoryMaxen la unidad del runner y ejecuta un runner por host, porque los runners que usan el mismo--base-diren un sistema de archivos comparten los repositorios clonados allí. Por ejemplo, en--capacity 1sin un hookcheckout, sus sesiones sobre un mismo repositorio pueden trabajar en el mismo checkout a la vez. Para ejecutar varios runners en un host, da a cada uno un contenedor.
--capacity mayor que uno, las sesiones comparten un límite. Cuando lo alcanzan, el kernel no elige por sesión qué procesos matar, por lo que el uso de memoria de una sesión puede terminar las demás.
En Kubernetes, no puedes separar las sesiones con ninguno de los valores del ajuste singleProcessOOMKill del kubelet. Tampoco puedes limitar la memoria total de una sesión con un ulimit de memoria en tu script de envoltura. El kernel aplica un valor como ulimit -v a cada proceso por separado.
Deja margen bajo los límites de procesos e hilos
Para dimensionar un límite de procesos e hilos, mide lo que necesita una sesión y multiplícalo por el número de sesiones que comparten el límite. Cuando esas sesiones alcanzan el límite, el kernel rechaza nuevos procesos e hilos, por lo que los comandos, compilaciones y pruebas en cualquiera de ellas pueden fallar. La llamada rechazada devuelveEAGAIN, cuyo mensaje estándar es Resource temporarily unavailable.
Supón que una sesión alcanza un pico de 300 procesos e hilos, y cuatro sesiones comparten el límite. Juntas necesitan 1,200, y con un límite de 2,400, dejas margen para compilaciones que necesiten el doble.
Dónde establecer cada límite
Cada límite de esta tabla cuenta tanto hilos como procesos, ypids.max es el archivo que contiene el límite de un cgroup:
Cuando no estableces
TasksMax, systemd aplica DefaultTasksMax, que de forma predeterminada es el 15% del menor entre kernel.pid_max, kernel.threads-max y el pids.max del cgroup raíz.
Mide el pico de una sesión
Para obtener los números de tus propios runners, mide una sesión en su host, que en Kubernetes es el nodo:1
Inicia un runner solo
Inicia un runner en
--capacity 1 sin nada más en su contenedor o unidad de systemd, solo en su host y en su entorno:- Su host: usa uno que no ejecute ningún otro runner. Así podrás identificar su proceso en el siguiente paso.
- Su entorno: haz que sea el único runner allí, por ejemplo en un entorno que crees para la prueba. Las sesiones se enrutan a un entorno y no a un runner.
2
Encuentra el directorio de cgroup del runner
En el host, no dentro del contenedor, encuentra el ID de proceso del runner, por ejemplo con
ps. Lee /proc/<pid>/cgroup para ese ID:- Una línea contiene
:pids:: el host usa cgroup v1, y el directorio es/sys/fs/cgroup/pidsseguido del texto que aparece después de:pids: - La única línea comienza con
0::: el host usa cgroup v2, y el directorio es/sys/fs/cgroupseguido del texto que aparece después de0::
3
Ejecuta una compilación representativa y registra el pico
Ejecuta una sesión a lo largo de una compilación representativa y toma el pico del directorio de cgroup del runner:
- El directorio tiene
pids.peak: lee el archivo cuando termine la compilación - El directorio no tiene
pids.peak: leepids.currentuna vez por segundo mientras se ejecuta la compilación y conserva el valor más alto. Un pico entre dos lecturas pasa desapercibido, así que trata ese valor como una estimación baja.
4
Multiplica por las sesiones que comparten el límite
- El Pod, contenedor o unidad de un runner: multiplica el pico por el
--capacitydel runner - Un límite que comparten varios runners: multiplica el pico por la suma de sus capacidades
5
Establece el límite por encima del resultado
Con un límite más alto, dejas margen para compilaciones más pesadas, y con uno más bajo, detienes antes una fork bomb.
Comprueba un límite pids.max
- Comprueba si se alcanzó
pids.max: busca en el registro del kernel del host, por ejemplo condmesg, una línea que contengacgroup: fork rejected by pids controller iny la ruta de un cgroup. El kernel la registra solo la primera vez que rechaza un proceso o hilo en ese cgroup, así que busca en todo el registro. - Consulta el límite vigente: lee
pids.maxen el directorio de cgroup del runner y en cada directorio por encima de él. El valor más bajo es el límite vigente, ymaxsignifica que no hay límite en ese nivel.
Kubernetes
El runner sirveGET /healthz en el puerto 8080 por defecto, configurable con --health-port, por lo que los sondeos de Kubernetes funcionan sin configuración adicional. El punto final devuelve 200 siempre que el proceso esté vivo, por lo que los sondeos a continuación detectan un proceso muerto, no uno atascado; para detectar un runner que dejó de sondear, alerte en la serie last_poll_age_seconds de /metrics. El Deployment a continuación monta el secreto del entorno desde un Secret de Kubernetes, apunta los sondeos de vivacidad y preparación a /healthz, y establece un período de gracia de terminación de 90 segundos. Consulte Tiempo de apagado para saber por qué importa el período de gracia.
El manifiesto no establece resources de CPU o memoria en el contenedor del runner. Agregue un bloque dimensionado para la capacidad que ejecuta, como Dimensione CPU y memoria para sesiones describe.
claude-runners. Cree el espacio de nombres primero:
(umask 077 && cat > ./environment-secret), pega el secreto, presiona Enter y luego Ctrl-D. Después crea el Secret y elimina el archivo:
Docker Compose
El servicio Compose a continuación reinicia el runner siempre que sale, lo que cubre tanto bloqueos como la salida normal después del drenaje. Una política de reinicio de Docker reinicia el mismo contenedor con su capa escribible intacta, por lo que el runner regresa en un sistema de archivos reutilizado en lugar del nuevo que la postura de endurecimiento recomienda; use esta receta para evaluación, y para producción recrear el contenedor por ejecución o use un orquestador que lo haga. Docker espera más tiempo antes de cada reinicio de un contenedor que sigue saliendo, hasta un límite máximo, por lo que un runner que no puede iniciarse no sigue reiniciándose en un bucle cerrado bajo esta receta. Cuando el runner sale describe qué verificar cuando eso sucede.Tiempo de apagado
Después deSIGTERM, un runner necesita tiempo para cerrar sus sesiones limpiamente antes de que tu orquestador lo mate. Registra cuánto tiempo necesita al inicio, en una línea que contiene This runner needs up to 80s con la configuración predeterminada. Establece el tiempo de parada de tu orquestador en al menos esa cantidad de segundos: terminationGracePeriodSeconds en Kubernetes, stop_grace_period en Docker Compose, o el equivalente de tu plataforma. Kubernetes usa 30 segundos por defecto, así que sin este ajuste puede detener el pod antes de que el runner termine.
En SIGTERM, el runner deja de aceptar sesiones nuevas. Luego comienza un apagado ordenado, llamado drenaje, a menos que establezcas --defer-shutdown-max-min para retrasarlo. El drenaje tiene tres pasos:
- El runner espera hasta
--drain-wait-secsegundos,0por defecto, a que terminen los turnos que aún están en ejecución. - Termina el árbol de procesos de cada sesión, incluido cualquier comando que Claude aún estaba ejecutando, pero no un proceso que sigue en ejecución después de que su comando de shell terminó.
- Ejecuta el hook de ciclo de vida
post-session.
post-session aún está guardando trabajo no confirmado.
El tiempo registrado es la suma de estos valores:
--drain-wait-secpara el paso 1, 0 segundos por defecto--session-stop-grace-secpara el paso 2, 5 segundos por defecto--post-session-hook-timeout-secpara el paso 3, 60 segundos por defecto- Un margen fijo de 15 segundos
- 30 segundos más cuando
--push-outcome-on-releaseestá establecido
--capacity mayor no lo aumenta, porque el runner drena todas sus sesiones al mismo tiempo.
Deja más tiempo si estableces alguno de estos flags:
- Con
--retire-at: deja suficiente tiempo entre el tiempo de retiro y el momento en que el host se detiene para que terminen los turnos típicos, más la espera de las tareas en segundo plano que describe Ciclo de vida del runner, más el tiempo registrado. Calcula el tiempo de retiro en cada lanzamiento, por ejemplodate +%smás la vida útil prevista del runner. - Con
--defer-shutdown-max-min: el tiempo de parada también debe cubrir los minutos que configuras y una espera adicional antes de que comience el drenaje, 75 segundos con la configuración predeterminada. Diferir el drenaje más allá de la primera señal explica ambos. El runner también registra este tiempo más largo al inicio.
Dejar que los turnos en ejecución terminen antes de un reinicio
Para dejar que los turnos en ejecución terminen antes de un reinicio gradual, establece--drain-wait-sec y aumenta tu tiempo de parada a al menos esa espera más 80 segundos. Con la espera predeterminada de 0, un reinicio interrumpe cualquier turno que aún esté en ejecución, y la sesión se reanuda en otro runner sin su trabajo no enviado.
--drain-wait-sec: aproximadamente lo que duran los turnos y las tareas en segundo plano que quieres dejar terminar. Es un máximo: la espera termina antes si ellos terminan primero.- Tu tiempo de parada:
terminationGracePeriodSecondsen Kubernetes,stop_grace_perioden Docker Compose. Por ejemplo, con--drain-wait-sec 120, reemplaza el90en la receta de Kubernetes o de Docker Compose por200. - Si cambiaste otros ajustes de apagado: los 80 segundos corresponden a la configuración predeterminada. Inicia un runner con tus flags y usa al menos el número de su línea de inicio
This runner needs up to, que incluye la espera. Con--defer-shutdown-max-minestablecido, usa el número mayor de la siguiente línea de inicio, después deSize the stop timeout to at least. Tiempo de apagado enumera lo que aumenta estos números.
Publicar un mensaje de estado antes de un reinicio
Antes de un reinicio que vaya a interrumpir sesiones, pide a un Owner que publique un mensaje de estado en el entorno desde la página de administración Cloud environments. En claude.ai/code, los miembros ven el mensaje en sus sesiones de ese entorno. Pide al Owner que establezca una fecha de vencimiento al publicarlo, o que elimine el mensaje una vez que los runners vuelvan a estar activos.Diferir el drenaje más allá de la primera señal
Establezca--defer-shutdown-max-min <n> si desea que un runner que está reiniciando continúe sirviendo las sesiones que mantiene durante hasta n minutos, en lugar de drenarlas en la primera señal. En la primera SIGTERM o SIGINT, el runner deja de tomar trabajo nuevo y continúa sirviendo las sesiones que mantiene. Sigue sondeando para que el plano de control no reencole esas sesiones. Requiere Claude Code v2.1.238 o posterior.
Lo que sucede con las sesiones que el runner mantiene después de la primera señal
En las primeras dos etapas que siguen a la señal, el runner libera sesiones, y una sesión liberada se reanuda en un runner nuevo cuando su usuario envía su siguiente mensaje. Contando desde la primera señal, el runner se mueve a través de tres etapas:- Durante los primeros
nminutos: el runner sirve sus sesiones normalmente y sigue aplicando--startup-timeout-miny--kill-session-after-min. Si también establece--release-idle-session-min, el runner libera cualquier sesión cuyo usuario ha estado inactivo ese tiempo; sin ella, las sesiones inactivas permanecen en el runner. - Cuando los
nminutos se agotan: el runner libera cada sesión que aún mantiene, inactiva o no. El runner espera a que la sesión de mitad de turno termine su turno, y hasta 60 segundos más para las tareas de fondo de un turno, antes de liberar esa sesión. - Cuando la gracia posterior a la liberación se agota: el runner drena cualquier sesión que aún mantiene, y el plano de control reencola cada sesión drenada a otro runner de inmediato. La gracia posterior a la liberación comienza cuando los
nminutos se agotan y es 75 segundos en valores predeterminados. Si establece--drain-wait-secpor encima de 60 segundos, la gracia posterior a la liberación es--drain-wait-secmás 15 segundos en su lugar.
--defer-shutdown-max-min. Una vez que un drenaje está en marcha, la siguiente señal fuerza la salida del runner. Eso se mantiene si una segunda señal o la gracia posterior a la liberación que se agota inició el drenaje.
Dimensione el tiempo de parada
Dale al tiempo de parada de tu host al menos la suma de tres partes: losn minutos que configuras, la gracia posterior a la liberación, y el drenaje que describe Tiempo de apagado. Con la configuración predeterminada, la gracia posterior a la liberación es 75 segundos y el drenaje tarda hasta 80 segundos, así que deja n minutos más 155 segundos. El runner imprime esta suma al inicio siempre que --defer-shutdown-max-min está establecido.
Si el tiempo de parada se agota antes de que el runner termine, las sesiones que aún mantiene no obtienen ningún hook post-session. El runner no se desregistra, así que el plano de control puede tardar unos minutos, y a veces más, en reencolar esas sesiones. Si no puedes dar al tiempo de parada esa suma, deja --defer-shutdown-max-min sin establecer para que el runner drene en la primera señal en su lugar.
Lo que alcanza un hook post-session en ejecución
El hookpost-session y el hijo de sesión de Claude cada uno se ejecutan en su propio grupo de procesos POSIX, separado del runner, por lo que los mecanismos de parada los alcanzan de manera diferente:
- Un
SIGTERMmientras el runner ya está drenando: fuerza la salida del runner inmediatamente, omitiendo lo que queda del drenaje. Sin--defer-shutdown-max-min, eso es el segundoSIGTERMque recibe el runner. Nada señala un hookpost-sessionen ejecución, por lo que en un host desnudo donde un proceso init adopta huérfanos, termina por su cuenta, pero sin supervisión: su presupuesto de tiempo ya no se aplica, y una escritura en la tubería de registro cerrada puede matarlo conSIGPIPE, así que un hook que necesita sobrevivir a una salida forzada allí debe redirigir su propia salida a un archivo. En las recetas de contenedor en esta página el runner es el PID 1 del contenedor y su salida termina el contenedor, y bajo elKillMode=control-grouppredeterminado de systemd la matanza de cgroup alcanza el hook también, como la entrada Matanzas de cgroup describe; en ambos, trata una salida forzada como fatal para el hook y confía en el período de gracia en su lugar. - Señales de grupo de procesos, como
kill -- -<pid>en un script de envoltura, control de trabajo de shell, o un vigilante de grupo: alcanzan el runner y un subproceso de hookcheckouten ejecución, que permanece adjunto al grupo deliberadamente, pero no un hookpost-sessionen ejecución o el hijo de sesión. - Matanzas de cgroup, como el
KillMode=control-grouppredeterminado de systemd o elSIGKILLque Kubernetes entrega a todo el contenedor cuandoterminationGracePeriodSecondsexpira: alcanzan todo, incluido el hook. El aislamiento de grupo de procesos no protege contra estos, que es por qué el período de gracia debe cubrir todo el drenaje. - El tiempo de espera propio del hook: cuando un hook excede
--post-session-hook-timeout-sec, el runner envíaSIGTERMa todo el grupo de procesos del hook, luegoSIGKILLdos segundos después, por lo que un trabajador que el hook bifurcó, como tar, rsync, o git, termina con el shell de envoltura en lugar de sobrevivir como un huérfano. La supervisión del runner termina una vez que el stdio del hook se cierra: un trabajador que redirigió su propia salida a un archivo y sobrevive a la etapaSIGTERMestá más allá del alcance del runner.
post-session aún se están ejecutando, para que pueda distinguir un drenaje tranquilo de uno que está en mitad de una instantánea.
Mantenga el directorio base y la capacidad idénticos en todos los runners
Si un runner muere en mitad de sesión, el servidor reencola la sesión y otro runner en el entorno la recoge. Ese runner deriva la ruta de checkout de su propio--base-dir y --capacity: --capacity 1 verifica directamente bajo --base-dir, y un --capacity por encima de 1 usa worktrees por sesión en su lugar. Cuando los runners en el mismo entorno usan valores diferentes para cualquiera de las banderas, el directorio de trabajo de la sesión reanudada cambia, y las rutas absolutas que el agente registró anteriormente, en ediciones, llamadas de herramientas, o sus propias notas, apuntan a una ubicación que ya no existe.
Usa el mismo --base-dir y --capacity en cada runner de un entorno, y no uses un valor por host como un ID de instancia o nombre de host. Pasa ambos flags de forma explícita, como hacen las recetas de Kubernetes y Docker Compose, para que ninguno de los valores dependa de un valor predeterminado.
El directorio base tiene como valor predeterminado /workspace, con la excepción que la fila de referencia --base-dir registra. El runner necesita acceso de escritura a él. Al inicio, antes de registrarse, el runner crea el directorio y confirma que puede escribir en él, y sale con cannot create or write to base directory cuando no puede. Un runner iniciado como root crea el /workspace predeterminado en sí. Para un runner que no es root, cree el directorio y dé al usuario del runner la propiedad antes de iniciar el runner, o apunte --base-dir a un directorio que ese usuario ya posee.
Reutilice un checkout precalentado
Para repositorios grandes, el clon puede dominar el inicio de sesión. Para omitir el clon frío, proporciona tú mismo un clon en la ruta donde el runner mantiene el suyo. Sin hookcheckout, el runner mantiene un clon canónico por repositorio en <base-dir>/<repo-owner>/<repo> y lo reutiliza entre sesiones:
- Con
--capacity 1: el runner obtiene la ref solicitada, desasociaHEADy hace un reinicio duro a ella, lo cual es casi instantáneo cuando ha cambiado poco. - Con un
--capacitymayor que uno: el runner obtiene en ese clon y luego extrae un worktree separado a partir de él para cada sesión. Un clon precalentado ahorra la descarga, pero no el checkout.
- Clon en la imagen: construya el clon en su imagen de runner en esa ruta. Cada contenedor nuevo comienza con el clon precalentado sin reutilizar un disco.
- Clon en un volumen persistente: en runners que prebloquea a la cuenta de un usuario con
--lock-to-account, apunte--base-dira un volumen persistente, para que el disco solo sirva esa cuenta. Un runner prebloquado nunca recoge sesiones de canal de Claude Tag, por lo que esta opción no se aplica a runners que las sirven.
-
Profundidad del clon: crea el clon sin
--depth, con o sin--single-branch. El runner nunca pasa--depthcuando obtiene en un clon con un remotoorigin, incluso si configurasCLAUDE_RUNNER_FETCH_DEPTH. Con un clon completo, cada sesión descarga solo los commits que le faltan al clon. Con un clon superficial, una sesión en una rama que se bifurcó antes del commit más antiguo del clon descarga todo el historial de esa rama. -
Los cambios rastreados se reinician, los archivos sin rastrear persisten: con
--capacity 1, cada sesión comienza desde un reinicio duro que borra las modificaciones rastreadas de la sesión anterior, pero el runner nunca ejecutagit clean, por lo que los archivos sin rastrear de las sesiones anteriores del propietario bloqueado permanecen en el árbol. -
Directorios por sesión persisten también: junto al checkout, el runner crea entradas por sesión bajo
<base-dir>/_sessions/para cada sesión que ejecuta. El directorio de configuración de Claude de la sesión contiene una copia local de la transcripción de la conversación. Junto a él se encuentran los archivos cargados de la sesión, cuando la sesión tiene alguno. El directorio de sesión también se encuentra allí: contiene cualquier worktree por sesión y checkouts de hookcheckoutmientras se ejecuta la sesión, y mantiene cualquier otra cosa que Claude escribió en él. Por defecto, el runner deja estos en su lugar cuando termina la sesión, por lo que en un disco que sobrevive al proceso del runner se acumulan. Cada sesión se ejecuta como el usuario del runner, por lo que cualquier sesión posterior que ese disco sirva puede leerlos. Si mantiene un--base-dirpersistente, dimensione el volumen para ese crecimiento. Lo mismo se aplica a cualquier configuración que reinicie el runner en el mismo sistema de archivos, incluida la receta de Docker Compose. -
Con
--remove-session-state, los directorios por sesión no persisten: inicie el runner con--remove-session-statepara que elimine los directorios por sesión de cada sesión cuando termina la sesión. La eliminación es de mejor esfuerzo: los directorios permanecen cuando el runner se mata antes de que se ejecute su limpieza. El clon canónico y los archivos que una sesión escribió en otro lugar del host, como el directorio temporal, permanecen independientemente. -
Con el proxy de git, el reinicio se convierte en un checkout: con
--use-anthropic-git-proxy, el runner sanitiza el.git/del clon antes de cada sesión. Mantiene el almacén de objetos y el estado superficial, y elimina el índice. Sin el índice, cada sesión comienza con un checkout completo del directorio de trabajo en lugar de un reinicio casi instantáneo. El runner sigue obteniendo en el clon existente en lugar de volver a clonar el repositorio. Los precalentamientos de submódulos no son compatibles con el proxy. -
Con el proxy de git, un precalentamiento con
reftablepierde sus refs: cuando el runner sanitiza.git/antes de cada sesión, mantiene.git/refs/y elimina el directorio.git/reftable/. Un clon precalentado con el formato de refsreftablede git pierde así sus refs, por lo que debes precalentar el clon con el formato de refsfilesde git en su lugar. - Los clones largos no necesitan solución alternativa: el runner limita cada operación de git con un vigilante de 120 segundos sin progreso y un límite duro de 30 minutos, no un tiempo de espera plano, por lo que un clon frío lento que sigue reportando progreso se completa.
Confirma que el runner usa tu clon
Para confirmar que el runner usa tu clon, inícialo con--log-level debug e inicia una sesión en el repositorio. El runner registra Prefetched repo found at y la ruta del clon. Si en cambio registra No prefetched repo, fresh init at, no encontró ningún clon utilizable en la ruta de esa línea. Comprueba que tu clon esté en esa ruta, que pertenezca al usuario del runner y que tenga un remoto llamado origin.
Si el runner encuentra tu clon y las sesiones siguen iniciándose lentamente, comprueba si el clon es superficial.
Fije la versión
De forma predeterminada, el proceso hijo de cada sesión ejecuta el binario propio del runner, y el runner desactiva la actualización automática dentro de las sesiones que genera. Con ese valor predeterminado, cada sesión ejecuta la versión que instalaste en el host o que incluiste en la imagen. Una actualización a nivel de host entra en vigor la próxima vez que el runner se inicia. Elige qué versión ejecutan tus sesiones y cuándo cambia:- Antes de fijar una versión: consulta las versiones de Claude Code que requieren los modelos para cada modelo que usan tus sesiones. Si un modelo requiere una versión más reciente que la que ejecutan tus sesiones, el servidor rechaza las solicitudes para ese modelo con Claude Code no admite este modelo.
- Para mantener una flota en una versión: construya la imagen con una versión fijada, o en un host desnudo instale una versión específica y desactive las actualizaciones automáticas
- Para actualizar una flota fija: lee las entradas del changelog entre tu versión y la que vas a instalar; luego instala la versión más reciente o vuelve a crear la imagen y reinicia los runners
- Para actualizar runners bajo demanda: lee las entradas del changelog entre tu versión y la que vas a instalar; luego cambia la imagen que inicia tu hook
spawn-runner. Cada runner nuevo obtiene la nueva versión. Un runner que ya está en ejecución, incluido un runner en espera que inició--min-idle, conserva su versión hasta que termina. No lo reinicies, porque su orden de trabajo es de un solo uso. - Si las sesiones inician un binario diferente: si usas
--exec-patho un hookcommandpara iniciar un binario de Claude Code distinto del propio del runner, las sesiones ejecutan la versión de ese binario. Actualiza ese binario cuando actualices el runner. - Plugins: los mercados de plugins tampoco se actualizan automáticamente; establezca
FORCE_AUTOUPDATE_PLUGINS=1en el entorno del runner para permitir que los plugins se actualicen automáticamente mientras el binario permanece fijado
Escale la flota
Su orquestador decide cuándo agregar o eliminar runners. Debido al bloqueo de un propietario por runner, el recuento de réplicas mínimo es el número de usuarios y agentes de Claude Tag que espera que estén activos simultáneamente;--capacity controla el paralelismo dentro de las sesiones de un propietario, no entre propietarios.
Dos enfoques de escalado están disponibles:
- Flota fija: ejecute un conjunto estático de réplicas de runner y escale en las métricas de Prometheus que cada runner sirve
- Runners bajo demanda: ejecute el subcomando
claude self-hosted-runner orchestrator, que sondea Anthropic para sesiones que están en cola sin runner disponible e invoca su hookspawn-runnerpara arrancar uno por sesión. Consulte Runners bajo demanda.
Libera las sesiones inactivas en una flota fija
Si las sesiones permanecen en cola en una flota fija mientras todos los runners están en línea, la causa pueden ser las sesiones inactivas. De forma predeterminada, una sesión conserva su espacio después de que termina su turno, y su runner permanece bloqueado para el propietario de esa sesión. Si la tarea programada en la nube de tu equipo inicia una nueva sesión en cada ejecución, sus sesiones inactivas pueden llenar todos los espacios. Para comprobarlo, leeclaude_code_self_hosted_runner_session_idle_seconds en las métricas de Prometheus de cada runner. Cada sesión que mantiene un runner tiene su propia serie: 0 mientras la sesión está ocupada; de lo contrario, los segundos transcurridos desde que quedó inactiva. Elige el tiempo de inactividad que permitirías: más largo que las pausas que las personas hacen a mitad de una tarea, y lo bastante corto como para que cada ejecución se libere antes de que comience la siguiente de la tarea programada. Compara las series con ese valor.
- Series por encima de ese tiempo, como
1800para 30 minutos: las sesiones inactivas ocupan esos espacios, así que establece--release-idle-session-min - Todas las series por debajo: ninguna sesión ha estado inactiva tanto tiempo. Agrega runners o establece
--kill-session-after-minpara las sesiones que nunca quedan inactivas. - Un runner sin series: ninguna sesión lo ocupa. Consulta Sessions stay queued en Solución de problemas.
- Un runner cuyas métricas no responden: es posible que su proceso haya terminado. Consulta An environment that looks healthy en Cuando el runner termina.
--release-idle-session-min y ese tiempo en minutos, como --release-idle-session-min 30. El runner libera entonces cualquier sesión que permanezca inactiva ese tiempo, ya sea que la haya iniciado una tarea programada o una persona.
- Trabajo sin enviar: una sesión liberada se reanuda con su siguiente mensaje, sin el trabajo que no había enviado con push. Para conservar sus commits, consulta Las sesiones reanudadas pierden el trabajo sin enviar.
- El runner termina: un runner que libera su última sesión inicia una salida normal. Haz que tu supervisor lo reinicie en un sistema de archivos limpio, como se describe en Refuerza tu despliegue.
- Sesiones que nunca quedan inactivas: establece también
--kill-session-after-min, porque algunas sesiones no cuentan como inactivas
Problemas conocidos y limitaciones
Las siguientes son las limitaciones en esta versión, con soluciones alternativas donde exista una.El tráfico del conector sale de su red
Anthropic llama herramientas de conector desde su propia infraestructura en lugar de desde su runner. Las herramientas de conector son los conectores de claude.ai, como GitHub, Slack y Linear. Cuando Claude usa un conector en una sesión autohospedada, ese tráfico va a través deapi.anthropic.com en lugar de originarse dentro de su límite de red.
Para mantener un conector fuera de sesiones autohospedadas, filtrelo con la configuración de política allowedMcpServers y deniedMcpServers. Claude Code aplica estas configuraciones a los conectores que Anthropic entrega así como a los servidores que configura desde el host del runner y los servidores que los usuarios agregan, por lo que si implementa una lista de permitidos para otros servidores, Claude Code bloquea conectores entregados también. Para mantener conectores disponibles junto con una lista de permitidos basada en URL, agregue entradas que coincidan con las rutas de proxy de Anthropic para conectores entregados:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
Algunas sesiones no cuentan como inactivas
Una sesión que mantiene una tarea de fondo que nunca termina no cuenta como inactiva, por lo que--release-idle-session-min no liberará la ranura de esa sesión. Una sesión que espera una aprobación solicitada desde dentro de una llamada de herramienta en ejecución tampoco cuenta como inactiva. Siempre establezca --kill-session-after-min junto con ella como un tope duro para que ninguna sesión pueda mantener una ranura indefinidamente.
--kill-session-after-min es un tope para sesiones descontroladas. En un runner en v2.1.260 o posterior, una sesión que alcanza el límite no se termina inmediatamente. El runner le da una ventana de gracia, 15 minutos por defecto, que puede cambiar con SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS:
- Si la sesión está esperando a su usuario, el runner la libera. Si su turno ha terminado y solo mantiene tareas de fondo, el runner espera hasta 60 segundos a que esas tareas terminen y luego la libera. La sesión se reanuda cuando su usuario envía su siguiente mensaje.
- Si un turno aún se está ejecutando, el runner espera a que el turno termine, o a que la sesión espere a su usuario, y luego la libera.
- Si la sesión aún está en el runner cuando la ventana de gracia termina, el runner la termina, y se pierde el trabajo de cualquier turno en ejecución. Un turno esperando una aprobación solicitada desde dentro de una llamada de herramienta en ejecución es una forma en que una sesión supera la ventana.
--kill-session-after-min 480 para 8 horas. Para liberar ranuras de conversaciones que se vuelven inactivas, use --release-idle-session-min en su lugar.
Limitaciones adicionales
- Las sesiones reanudadas pierden el trabajo sin push: un runner nuevo vuelve a clonar el repositorio desde su rama inicial, por lo que el trabajo al que la sesión no había hecho push se pierde.
- Para conservar el trabajo con commit: establece
--push-outcome-on-releaseen todos los runners del entorno, porque un runner sin el flag reanuda la sesión desde su rama inicial. Un runner con el flag hace, en la medida de lo posible, push de las ramas de resultado de la sesión antes de liberarla, y la sesión reanudada comienza desde esos commits. El push usa las credenciales de git propias del host del runner, incluso en un runner que usa git administrado por Anthropic. - Con un hook
checkout: a los repositorios obtenidos mediante un hook de ciclo de vidacheckoutno se les hace push. En su lugar, guarda una instantánea de ellos desde el hookpost-session. - Antes de habilitar el flag: restringe quién puede hacer push a las refs
claude/*en el remoto de origen. Al reanudar, el runner obtiene la rama a la que se hizo push previamente sin verificar quién hizo el push. - Los cambios sin commit se siguen perdiendo: para pedirle a Claude que haga commit y push cuando termine de responder, consulta Pedir a las sesiones que hagan push de su trabajo
- Para conservar el trabajo con commit: establece
- Un comando interrumpido por la pérdida de un runner puede volver a ejecutarse: cuando la sesión se reanuda en otro runner, Claude ve la llamada a herramienta interrumpida con un resultado que indica que su desenlace es desconocido.
- Un repositorio agregado a mitad de sesión puede fallar al clonarse: Claude lo clona con
git clonea través de HTTPS. En un runner sin--use-anthropic-git-proxy, la clonación falla con un error de autenticación de git si nada en el host puede leer el repositorio. Siempre que puedas, selecciona todos los repositorios que la sesión necesita al crearla. - Algunos conectores no aparecen en sesiones autohospedadas: un conector que aún no ha conectado en la configuración de claude.ai no se enumera en una sesión autohospedada, y la sesión no le pedirá que lo conecte. Conéctelo en Configuración primero, luego inicie una sesión nueva. Agregar un conector a una sesión ya en ejecución tampoco hace que sus herramientas estén disponibles para Claude; inicie una sesión nueva para recoger un conector recién agregado.
- Cuando alguien archiva o elimina una sesión, o libera su runner desde el menú de la sesión: no cuentes con el push de
--push-outcome-on-release. Para guardar los commits en esos casos, haz push deHEADa una rama de rescate desde un hookpost-session. Si partes del ejemplo de esa sección, haz que haga push incluso cuando no haya nada sin commit, porque solo hace push después de un commit de instantánea.
Reportar un problema
Para problemas con entornos autohospedados, contacte a su equipo de cuenta de Anthropic.Solución de problemas
Para un diagnóstico guiado, ejecute el subcomando doctor en el host del runner. El subcomando doctor inicia una sesión interactiva de Claude Code con los registros y el estado del runner adjuntos. Inicie sesión conclaude auth login en ese host primero para que la sesión pueda consultar su entorno, sus runners y sus sesiones en cola. Sin ese inicio de sesión, por ejemplo cuando el host se autentica con una clave API, se limita al punto final de salud local, las métricas y el registro del runner, y lee el registro solo si inició el runner con --log-file.
-
El runner no aparece en el entorno: confirme que el host pueda alcanzar
api.anthropic.comsobre HTTPS, que el secreto del entorno sea actual y que el reloj del host esté dentro de cinco minutos de la hora real; un sesgo mayor causa que la autenticación falle. El runner registra[runner:fatal]con el motivo del rechazo en caso de fallo de autenticación. -
El runner se cierra al inicio con
cannot create or write to base directory: el runner no puede crear ni escribir en--base-dir, que por defecto es/workspace. Corrija la propiedad del directorio o apunte--base-dira una ruta escribible, como se describe en Mantener el directorio base y la capacidad idénticos en todos los runners. Si el runner registra[runner:fatal]diciendo que la verificación del directorio base agotó el tiempo de espera, el directorio está en un montaje NFS o CSI colgado. Verifique la salud del montaje en lugar de los permisos. El runner imprime ambas fallas de inicio en stderr antes de abrir--log-file, así que búsquelas en la terminal o en los registros del contenedor de su plataforma en lugar del archivo de registro. Antes de v2.1.225, el runner no verificaba el directorio base al inicio, y esta configuración incorrecta fallaba en las sesiones después de la recogida. -
El runner se cierra al inicio con
RegisterRunner failed:y un error de certificado TLS: el error puede serself signed certificate in certificate chain, por ejemplo. El runner no confía en la autoridad de certificación (CA) detrás del certificado que recibió, como ocurre detrás de un proxy que inspecciona TLS. EstableceNODE_EXTRA_CA_CERTSen el entorno del runner con un archivo PEM que contenga la CA raíz del proxy. No necesitas establecerla de nuevo para las sesiones, que la heredan. Git no la lee: para clonar a través de ese proxy, establece tambiénGIT_SSL_CAINFO. -
Tu script de inicio imprime un error de escritura que contiene
Resource temporarily unavailabley se cierra: tu script comparte su stdout o stderr con el procesoclaude self-hosted-runneru orquestador que inicia, el cual ha cambiado esa tubería o socket compartido al modo no bloqueante. Cuando la tubería está llena, una escritura de tu script falla con este error del sistema operativo en lugar de esperar, y conset -eel script se cierra. Para evitar el error, envía la salida del runner o del orquestador a un archivo que el usuario de tu script pueda crear. Agrega>> "$HOME/claude-runner.log" 2>&1al comando que lo inicia, antes de cualquier&que lo envíe a segundo plano:La salida propia de tu script sigue yendo adonde iba antes, mientras que lo que el runner escribe en stdout y stderr, incluidos sus errores de inicio, va a ese archivo. Usa esta redirección del shell en lugar de solo--log-file, que replica el registro en un archivo mientras el runner sigue escribiendo en stdout y stderr. Si ya pasas--log-file, mantenlo también, porque la redirección no cambia lo que recibe ese archivo. -
El orquestador registra
poll failed:con un error de certificado TLS y sigue reintentando: el orquestador no confía en la CA detrás del certificado que recibió, como ocurre detrás de un proxy que inspecciona TLS. EstableceNODE_EXTRA_CA_CERTSen el entorno del orquestador con un archivo PEM que contenga la CA raíz del proxy. Establécela también para los runners que inicia tu hookspawn-runner. Por ejemplo, una línea puede terminar enpoll failed: self signed certificate in certificate chain — retrying in 1s, con las palabras de la biblioteca TLS entre los dos puntos y el guion. Para confirmar la corrección, comprueba queconnectedseatrueen el cuerpo de/healthzdel orquestador. -
Las sesiones permanecen en cola: cada runner en línea puede estar bloqueado a un propietario diferente. Verifique la métrica
claude_code_self_hosted_runner_locked_accountde cada runner o el campolocked_accountde su línea de registro[runner:health]para ver quién la mantiene. Ambos muestran el correo electrónico del propietario solo después de que el runner haya recibido un token de sesión que lleve un reclamoact.email, que las sesiones de un agente Claude Tag nunca hacen. Sin el reclamo, el runner no emite ninguna serielocked_accounty registralocked_account=yes, lo que le indica que el runner está bloqueado pero no a qué propietario. Agregue réplicas o espere a que un runner existente se drene y reinicie. Si el entorno usa runners bajo demanda, verifique el orquestador en su lugar; consulte On-demand runners. -
Las sesiones fallan inmediatamente después de la recogida: abre la sesión en claude.ai/code para ver el error. Si el error muestra que git no pudo autenticarse, revisa las credenciales de git del runner. En un runner iniciado con
--use-anthropic-git-proxy, consulta Cuando las sesiones no se inician en un runner con el proxy de git. Un directorio base no escribible detiene el runner al inicio en lugar de hacer fallar las sesiones. Consulta la entrada El runner se cierra al inicio concannot create or write to base directoryen esta lista. -
Una sesión se recoge, pero Claude nunca responde su primer mensaje: si tus runners llegan a
api.anthropic.coma través de un proxy, configúralo para que deje pasar la respuesta/worker/events/streamde la sesión a medida que llega. Aumentar los tiempos de espera del runner no ayuda. Para confirmar la causa en el registro del runner, consulta Una sesión se recoge, pero Claude nunca responde su primer mensaje. -
Las sesiones no se inician en un runner que estableció
--use-anthropic-git-proxy: busca en el registro del runneraccess denied by the git proxy, o un error de git que nombre una dirección deapi.anthropic.comque contenga/git_proxy/. Para saber si Anthropic atendió la sesión y corregir la causa, consulta Cuando las sesiones no se inician en un runner con el proxy de git. -
Las sesiones no pueden alcanzar la red a través de un proxy de salida autenticador: cuando la fuente que estableció con
--proxy-authorization-commando--proxy-authorization-filefalla, agota el tiempo de espera después de 30 segundos o produce un valor vacío, el runner responde esa conexión con502 Bad Gatewayy registra por qué. El runner redacta stderr del comando en ese registro y nunca registra el valor del encabezado. Con--proxy-authorization-command, ejecute el comando usted mismo en el host para confirmar que imprime el valor de encabezado completo en stdout. Si el runner se cierra al inicio concould not start the proxy-authorization listener, no pudo abrir su oyente de loopback. -
El runner registra líneas
Poll failedque contienenrejecting the malformed poll response: el runner recibió una respuesta de sondeo de trabajo cuyo cuerpo no es el JSON esperado de la cola. Una causa es un proxy interceptor o un portal cautivo que responde con su propia página. Si el runner se conecta a través de un proxy, configura el proxy para que pase las respuestas deapi.anthropic.comsin alterarlas. El runner rechaza la respuesta malformada, la cuenta bajo el tipotransportde la métricaclaude_code_self_hosted_runner_poll_errors_total, y reintenta según el cronograma de sondeo fallido descrito en Session lifecycle. El runner sigue atendiendo sus sesiones activas. Antes de v2.1.246, el runner interpretaba una respuesta así como una cola de trabajo vacía, lo que podía terminar sus sesiones activas o hacer que se cerrara. -
Una sesión falla con
no longer exists on the remoteocouldn't find remote ref: el runner interpreta elcouldn't find remote refde git como una rama eliminada. La rama aún puede existir. Búscala en tu host de git, exactamente con el nombre que aparece en el mensaje, y luego revisa cada causa que corresponda:- La rama ya no existe: restáurala, como pide el mensaje.
- La rama existe y no administras el runner: envía al administrador del runner el mensaje completo e indícale que la rama existe.
- La rama existe y el runner tiene un hook
checkout: un comando de git en tu hook puede imprimircouldn't find remote refen stderr antes de que el hook termine con un código distinto de cero. Encuentra ese comando y corrige la ref que solicita o el remoto del que obtiene. El runner pasa a tu hook la revisión que pidió la sesión comoCLAUDE_RUNNER_REPO_REF. - La rama existe y el runner no tiene hook
checkout: el runner obtiene la rama por sí mismo. Si se aplica un--git-host-rewriteal repositorio, confirma que el host al que reescribe tenga la rama con el mismo nombre, y envía la rama allí si no la tiene. - La rama existe y el runner es anterior a v2.1.301: actualiza el runner como se describe en Fijar la versión, para que una falla distinta muestre su propio error. Antes de v2.1.301, otras fallas con
couldn't find remote refen su salida de error también se reportaban como una rama eliminada.
-
Claude Tag a veces no publica su respuesta en Slack: el recordatorio de respuesta es un hook que proporciona el plano de control. Confirma con
claude --versionque el runner sea Claude Code v2.1.229 o posterior, ya que los runners más antiguos ignoran los hooks proporcionados; confirma quepython3 --versionfuncione dentro de la imagen del runner, ya que los hooks ejecutanpython3; y confirma que ningún archivo de configuración desactive los hooks, ya seadisableAllHooksenmanaged-settings.json, en el~/.claude/settings.jsondel usuario del runner o en el.claude/settings.jsono.claude/settings.local.jsondel repositorio, o bienallowManagedHooksOnlyo unstrictPluginOnlyCustomizationque cubra los hooks en la configuración administrada. -
Una sesión comienza sin uno de sus repositorios: en un runner sin un hook
checkout, el host de git puede rechazar la verificación de acceso del runner para un repositorio que la sesión solo lee. El runner entonces omite ese repositorio, registra una línea[runner:warn] could not access context sourceque nombra el rechazo, e inicia la sesión en los restantes. El runner omite solo un rechazo claro: el host responde que el repositorio no fue encontrado, git no encuentra credenciales para el host, o la autenticación falla. Una falla de red, un tiempo de espera agotado, o un HTTP403aún falla el inicio de sesión, al igual que un rechazo para un repositorio al que la sesión envía resultados. El runner aún falla una sesión que omitir dejaría sin repositorio en absoluto. Con--use-anthropic-git-proxy, el runner omite solo un repositorio que el proxy de git mismo deniega. La verificación de acceso se ejecuta nuevamente cada vez que la sesión comienza en un runner, así que una vez que la identidad de git del runner tiene acceso de lectura, el siguiente inicio clona el repositorio. Antes de v2.1.274, cada uno de estos rechazos fallaba el inicio de sesión. -
Una sesión no se inicia con
This runner couldn't read: el runner le pidió al host de git un repositorio con sus credenciales de git y obtuvo una respuesta de no encontrado. El repositorio no existe, o esas credenciales no pueden acceder al repositorio.- Si administras el runner: otorga a sus credenciales de git acceso de lectura al repositorio y luego inicia una nueva sesión.
- Si iniciaste la sesión: si el repositorio no existe, inicia una nueva sesión con el repositorio correcto. Si el runner no puede acceder al repositorio, pide al administrador del runner que otorgue acceso y luego inicia una nueva sesión.
-
Las sesiones tardan minutos en iniciarse o reanudarse: una causa es un clon desde cero. En una flota que inicia cada runner sobre un disco vacío, sin hook
checkout, el runner clona de nuevo los repositorios de la sesión antes de iniciar Claude Code. Una sesión que se reanuda sobre un disco vacío vuelve a esperar el mismo clon. Para evitar el clon desde cero, proporciona un checkout precalentado. Si ya proporcionas uno, confirma que el runner lo usa. Cuando el runner hace el clon por sí mismo y se cierra después de una sola sesión, reduceCLAUDE_RUNNER_FETCH_DEPTHdesde su valor predeterminado de 50 commits para obtener menos historial. Si usas un hook, este se ejecuta en lugar del clon del runner, así que revisa lo que hace tu hook. Observa la métricaclaude_code_self_hosted_runner_session_init_duration_secondspara ver cuánto tardan los inicios. -
Los turnos fallan con un 401: cuando un turno termina con un 401 o 403 de la API de Anthropic, el runner obtiene un
CLAUDE_CODE_OAUTH_TOKENnuevo de Anthropic y lo pasa a la sesión. El turno fallido no se reintenta. Este token es de corta duración, y el runner lo rota a través del stdin de la sesión. Cuando una obtención falla, el runner registra una líneainference_token refresh failedque dice cuándo reintentará, y continúa reintentando mientras la sesión se ejecute. Si cada llamada comienza a fallar aproximadamente 30 minutos en una sesión, un script contenedor probablemente ha cortado stdin de la sesión, por lo que las rotaciones de token no pueden alcanzarlo; consulte Keep stdin and file descriptor 3 attached. Antes de v2.1.274, el runner dejaba de reintentar una obtención fallida después de algunos intentos y esperaba la siguiente programada. Un turno fallido no desencadenaba una obtención, por lo que cada turno fallaba con un 401 hasta la siguiente obtención programada. -
Un turno falla en claude.ai/code con
This model needs a newer version of Claude Code: la versión de Claude Code con la que tus runners inician sesiones es más antigua que la que requiere el modelo de la sesión, así que la API rechaza la solicitud. Actualiza los runners a la versión que pide el mensaje, o a una posterior, como se describe en Fijar la versión. Mientras tanto, las personas pueden elegir otro modelo en la sesión. -
El pod se mata a mitad del drenaje: aumente
terminationGracePeriodSecondsal menos al valor que el runner registra al inicio. Consulte Shutdown timing.
[runner:fatal], en stdout, y la salida de depuración en stderr, todo como líneas de texto sin formato en lugar de JSON. Las fallas del directorio base descritas en las entradas de solución de problemas anteriores se imprimen en stderr antes de ese punto. Captura ambas secuencias con --log-file, que también permite que self-hosted-runner doctor las siga, o con la recopilación de registros de tu plataforma.
Cada proceso secundario de sesión escribe un registro de depuración separado. En caso de fallo, el runner expone la cola del registro junto con la sesión en claude.ai/code. A menos que haya iniciado el runner con --remove-session-state, también mantiene el registro de una sesión fallida en el disco e imprime su ruta en el registro del runner.
Cuando el runner se cierra
No reinicie un runner bajo demanda, porque su orden de trabajo es de un solo uso. Un runner que se cierra justo después de que comienza necesita un manejo diferente al de uno que se cierra por cualquier otra razón.- Una salida normal: el runner terminó sus sesiones y se drenó, alcanzó su tiempo de jubilación, o se le indicó que se detuviera. Reinícielo para que el entorno tenga capacidad nuevamente. Runner lifecycle describe estas salidas.
- Un inicio fallido: el runner no puede iniciarse con la configuración o el host que se le proporcionó, por lo que se cierra segundos después de que comienza, y se cierra de la misma manera cada vez que lo reinicia. Reiniciarlo más rápido no ayuda. Alguien necesita leer su salida y corregir la causa.
- Pérdida de contacto: un runner que no puede comunicarse con Anthropic durante más tiempo que su lease, por ejemplo mientras su host está suspendido, puede ser eliminado del entorno. Cuando un runner eliminado se reconecta, se cierra. Su registro puede mostrar una línea
[runner:fatal]que contienerunner record gone server-sideo, tras una interrupción más larga,poll auth failed. El runner no vuelve a registrarse por sí solo, así que reinícialo.
Reconocer un inicio fallido
Cuando el runner no puede iniciarse, imprime una línea que dice por qué, y luego se cierra. Para la mayoría de las causas, la línea contiene[runner:fatal]. Para algunas causas, la línea comienza con error: en su lugar, incluido cuando el runner no puede analizar sus banderas, no puede leer el secreto del entorno, o no puede crear ni escribir en el directorio base. La siguiente línea luego apunta a --help.
La mayoría de las líneas de registro comienzan con una marca de tiempo y [self-hosted-runner], que la muestra a continuación omite. Por ejemplo, un runner iniciado con el proxy de git de Anthropic y una capacidad superior a uno imprime una línea como esta:
--log-file. El runner imprime una línea error: antes de abrir el archivo de registro, así que búsquela en la terminal o en los registros del contenedor, como Solución de problemas señala.
Estos también ayudan cuando lee un inicio fallido:
- Sin línea en absoluto: un runner que el host mata no imprime ninguno. Si la salida termina sin línea
[runner:fatal]y sin líneaerror:, verifique si el host u su orquestador detuvo el proceso, por ejemplo por exceder un límite de memoria. - El código de salida: el runner no reserva un código de salida para errores que se repiten en cada inicio. Se cierra con el mismo código para un error de configuración, como una combinación no compatible de banderas, y para una falla que puede aclararse por sí sola, como que la API permanezca inalcanzable a través de los propios reintentos del runner. Base la decisión de esperar más tiempo en qué tan pronto se cerró el runner, y lea la salida del runner para aprender por qué.
- Un entorno que se ve saludable: algunos pasos de inicio se ejecutan después de que el runner se registra con su entorno, como
--configure-gity la configuración de credenciales del proxy de git de Anthropic. Si uno de esos pasos falla, el entorno puede seguir listando ese runner durante algunos minutos después de que el proceso haya salido, y la página Cloud environments puede leer Healthy mientras ningún runner está recogiendo trabajo. Si las sesiones permanecen en cola en un entorno que se ve saludable, verifique si su supervisor está reiniciando el runner.
Reiniciar con una espera que crece
Cómo obtener una espera creciente depende de su supervisor.-
Kubernetes: el Deployment en esta página no necesita cambios. Después de que un contenedor se cierra, el kubelet por defecto espera antes de reiniciar el contenedor, y la espera crece en cada reinicio hasta un techo. La espera comienza de nuevo una vez que el contenedor ha estado ejecutándose durante un tiempo sin cerrarse.
El kubelet aplica la misma espera después de una salida normal cuando el contenedor se ejecutó solo brevemente. Un runner que se drena a menudo puede por lo tanto mostrar el estado
CrashLoopBackOfftambién, así que lea la salida antes de concluir que el runner no puede iniciarse. El comando a continuación lee la salida de la última ejecución de un pod del Deployment:Cuando la última ejecución fue un inicio fallido, la línea[runner:fatal]oerror:está entre las últimas líneas de la salida. Para leer la última ejecución de otro pod, nombre ese pod en lugar dedeploy/claude-runner. -
Docker y Docker Compose: la receta Compose en esta página no necesita cambios. Con
restart: always, Docker espera más tiempo antes de cada reinicio de un contenedor que sigue cerrándose, hasta un techo. Reemplace<container>con el nombre del contenedor en el comando a continuación, que lee cuántas veces Docker ha reiniciado el contenedor:El comando imprime un número. Un número que sigue aumentando significa que Docker sigue reiniciando el runner. -
Una unidad systemd: por defecto systemd espera el mismo
RestartSecantes de cada reinicio y no lo alarga, así que una unidad conRestart=alwaysreinicia un runner que no puede iniciarse en ese mismo intervalo cada vez. Cuando los inicios vienen lo suficientemente rápido como para alcanzar el límite de velocidad de inicio de la unidad, cinco inicios en 10 segundos por defecto, systemd deja de reiniciar la unidad. La unidad permanece detenida hasta que alguien la inicie de nuevo, lo que systemd permite una vez que el intervalo del límite de velocidad ha pasado o después desystemctl reset-failed. PorqueRestartSecse aplica a cada reinicio, un valor más largo también retrasa el reinicio después de una salida normal. Elija un valor que equilibre los dos, y alerte sobre el recuento de reinicio de la unidad. - Un bucle de shell o su propio supervisor: aplique la misma regla usted mismo. Comience con una espera de cinco segundos. Después de cada ejecución que terminó dentro de un minuto, duplique la espera para el siguiente reinicio, hasta cinco minutos. Después de una ejecución que duró un minuto o más, vuelva a cinco segundos.
Verificar por qué el runner sigue cerrándose
Cuando el runner ha salido justo después de comenzar varias veces seguidas, deténgase y verifique estos antes de reiniciarlo de nuevo.- La última línea
[runner:fatal]oerror:: dice por qué se detuvo el runner. Solución de problemas enumera las causas comunes. - La combinación de banderas: el proxy de git de Anthropic requiere
--capacity 1. Las recetas en esta página usan una capacidad más alta, así que bájela cuando agregue el proxy a una de ellas. - Lo que el servicio de su entorno puede alcanzar: si el runner comienza a mano y falla bajo su supervisor, compare el usuario, el directorio de inicio, el
PATH, y el límite de memoria.--configure-gity el proxy de git de Anthropic necesitan git en elPATHy un~/.gitconfigescribible. - El secreto del entorno: si revocó el secreto o lo escribió mal, el runner imprime una línea que contiene
RegisterRunner auth failed. - La pestaña Activity del entorno: abra el entorno y seleccione Activity. Si nuevos runners siguen apareciendo allí y ninguno recoge trabajo, su supervisor está reiniciando el runner.
Una sesión se recoge, pero Claude nunca responde su primer mensaje
Si un runner recoge una sesión pero Claude nunca responde el primer mensaje, y tus runners llegan aapi.anthropic.com a través de un proxy, el proxy puede ser la causa. Anthropic envía a una sesión sus mensajes mediante una única respuesta en streaming que permanece abierta. Si tu proxy la almacena en búfer, o la termina antes de transmitir algo, el primer mensaje nunca llega. Las demás solicitudes de la sesión siguen funcionando.
Para corregirlo, cambia cómo maneja el proxy esta solicitud:
- No almacenes la respuesta en búfer: déjala pasar a medida que llega. Su tipo de contenido es
text/event-stream. - Deja que la respuesta permanezca abierta: puede permanecer abierta hasta una hora. Si el proxy tiene un tiempo de espera por inactividad, mantenlo muy por encima de 15 segundos, porque Anthropic envía un keep-alive al menos con esa frecuencia por defecto.
Confirmar la causa en el registro del runner
Estos pasos son para runners con Claude Code v2.1.287 o posterior, con el valor predeterminado--log-level info. Los runners más antiguos registran estas líneas más tarde, o no las registran.
- Espera a que transcurra
--startup-timeout-min, 15 minutos por defecto. El runner entonces detiene la sesión y registra una línea que contieneno child output fory termina en— releasing. - Busca
SDKStartup: SSEen el registro del runner. Estas líneas siguen a una líneafinished: interrupted, unas líneas después de la del paso 1. La mayoría no tiene marca de tiempo, así que un recolector de registros puede mostrarlas en otro lugar. - Busca la línea en esta tabla:
Si el registro no tiene ninguna de estas líneas, o el registro del proxy no muestra nada anómalo, reporta un problema.
Evitar que la sesión lo vuelva a intentar
La sesión mantiene el mensaje pendiente, y un runner que puede atender al propietario de la sesión la recoge de nuevo. Hasta que cambies el proxy, la sesión espera de la misma manera cada vez. Para detenerlo, archiva la sesión en claude.ai/code.Qué sigue
- Personalice sesiones: scripts de envoltura, hooks de ciclo de vida, runners bajo demanda, servidores MCP, y permisos
- Pruebe de extremo a extremo: verifique una nueva imagen de runner desde CI antes de promoverla
- Referencia: cada bandera CLI, variable de entorno, y métrica