Skip to main content
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.
Un entorno autohospedado ejecuta sesiones en la nube de Claude Code en runners que usted implementa dentro de su red, y en producción esas sesiones ejecutan código dirigido por el modelo en nombre de todos los que pueden enviar una sesión al entorno. Esta página es para el operador que lleva un entorno funcional a producción. Funciona a través de la implementación en orden: qué bloquear antes de conectar sistemas reales, la salida que necesita la flota, cómo las sesiones se autentican en su host de git, las recetas de implementación en sí, y qué verificar cuando las sesiones se comportan mal.

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 1 y el --drain-grace-sec 0 predeterminado 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-proxy cuando 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 login guarda para tu cuenta, o un GH_TOKEN en el entorno del runner.
  • 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.254 en el espacio de nombres de red del contenedor de sesión
    El bloque se aplica a su script de envoltura y hooks de ciclo de vida también, ya que comparten el contenedor. Autentique cualquier intercambio de token con el JWT de sesión contra su propio servicio de token sobre salida permitida, o use una identidad web basada en archivos como IAM Roles for Service Accounts (IRSA) en Amazon EKS.
  • 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-account limita 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. El warn predeterminado registra una violación y aún genera la sesión, enforce rechaza la sesión, y off desactiva 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 regla Edit, Write, o NotebookEdit en permissions.allow, o una entrada sandbox.filesystem.allowWrite o allowRead
    • Un bloque env no vacío
    • Una anulación de postura del operador como sandbox.enabled: false
    La protección se ejecuta independientemente de --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.
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.ai y platform.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 a api.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 de api.anthropic.com. Consulta Servidores MCP.
Estos flujos del lado del host sí alcanzan 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.sh de claude.ai en el momento de la instalación.
  • claude auth login interactivo: inicia sesión a través de claude.ai, claude.com y platform.claude.com. La configuración guiada, el modo con sesión iniciada de doctor y 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 desde hcaptcha.com, *.hcaptcha.com y challenges.cloudflare.com. Si tu organización usa inicio de sesión único, ese navegador también puede ser redirigido a través de sso.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 incluye Bash, 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 encabezado Proxy-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 ejemplo Bearer <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_PROXY ni HTTP_PROXY contiene una URL http:// o https://. El runner lee ambas variables en mayúsculas o minúsculas, y no consulta ALL_PROXY.
  • Cualquiera de las banderas pasadas al subcomando del orquestador: self-hosted-runner orchestrator no 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 encabezado Proxy-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_PROXY e HTTP_PROXY que 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 de HTTPS_PROXY o HTTP_PROXY que no haya establecido, y fija NO_PROXY al 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-git para 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
Para repositorios en github.com, también puedes iniciar el runner con --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 = Claude y user.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.hooksPath apuntando a un directorio de hooks gestionado por el runner. Sus hooks commit-msg y prepare-commit-msg añaden a cada commit un tráiler Co-authored-by: para el creador de la sesión. El tráiler se construye a partir del correo electrónico en CCR_SESSION_ACCOUNT_EMAIL y se omite cuando esa variable no está establecida. Si tu imagen ya establece core.hooksPath y el runner no usa git gestionado por Anthropic, el runner conserva tu ajuste, omite instalar estos hooks e imprime una advertencia [runner:git].
La firma de commit requiere git 2.34 o más reciente; el runner verifica al inicio y sale con un error si su git es más antiguo. Esta bandera no configura credenciales de push, que aún proporciona en la imagen. En un runner con v2.1.280 o posterior, los commits que haces desde un hook de ciclo de vida 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:
Sin una identidad, 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.helper que devuelve un token de alcance mínimo
  • GIT_SSH_COMMAND apuntando a una clave de alcance estrecho
Cualquier mecanismo que configures debe funcionar sin pedir nada, porque el clon y el fetch integrados del runner desactivan las solicitudes que git, SSH y Git Credential Manager mostrarían de otro modo:
  • 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 su GIT_SSH_COMMAND si 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 entorno GIT_ASKPASS en su lugar.
Si su host de git rechaza la credencial, o no configuró una, el runner reintenta algunas veces y luego falla la preparación del repositorio cuando el repositorio es el que la sesión empuja resultados a. Para un repositorio que la sesión solo lee, Troubleshooting cubre cuándo el runner lo omite en su lugar. El runner no pasa estas configuraciones al entorno de la sesión. Mantenga cualquier programa que nombre en 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-release del runner y cualquier push que haga tu hook post-session siguen usando las propias credenciales de git del host del runner y su ruta de red hacia github.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.
Con --use-anthropic-git-proxy establecido, el runner elimina y reemplaza la configuración global de git del usuario con el que se ejecuta, y no guarda ninguna copia de seguridad. Lo hace al inicio y antes de cada sesión. Cualquier inicio de sesión o ayudante de credenciales que tuvieras ahí se pierde. Los ajustes que escribe --configure-git se conservan. Ejecuta el runner como un usuario dedicado o en un contenedor, nunca como tu propio usuario.
Mantén los ajustes de git que no son secretos, como la identidad y 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.
Las recetas de Kubernetes y Docker Compose en esta página usan --capacity 4. Si agrega --use-anthropic-git-proxy o CLAUDE_RUNNER_USE_GIT_PROXY=1 a una de ellas sin cambiar la capacidad a 1, el runner sale al inicio cada vez que su orquestador lo reinicia. Establezca --capacity 1 y ejecute más réplicas para paralelismo. Cuando el runner sale muestra la línea que imprime el runner.
Para activar el proxy de git, agrega --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:
Al inicio, el runner imprime 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-rewrite y --git-ssh-rewrite no 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 ACTIVE ni la línea withheld: 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 contiene remote: 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 dice GitHub 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.
Después de corregir la causa, vuelve a iniciar las sesiones fallidas.

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 un gh 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 gh integrado 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 gh de la sesión. Cuando lo hace, la línea [runner:session] governed git ACTIVE que el runner registra para la sesión muestra gh_path_shim=true. Cuando no lo hace, la sesión no tiene gh
  • jq: instala jq en la imagen si quieres que --jq funcione
  • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: si el entorno de la sesión la establece, Claude Code no proporciona el gh integrado, y la sesión no tiene gh
Cuando la imagen incluye la GitHub CLI, las sesiones la usan.

Confíe en una autoridad de certificados privada con git gestionado por Anthropic

Esta sección aplica si establece GIT_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 ejemplo GIT_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.
Para conexiones de git que llevan el token de una sesión a git gestionado por Anthropic, el runner aplica las dos variables de la siguiente manera. Un hook 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.sslCAInfo que nombra tu archivo en lugar de la variable, más entradas http.<url>.sslCAInfo que verifican git gestionado por Anthropic contra el archivo por sesión.
    • Hooks checkout y post-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=false en lugar de la variable, así que las verificaciones permanecen desactivadas para otros hosts. También obtiene entradas http.<url>.sslVerify=true que mantienen las verificaciones activadas para git gestionado por Anthropic.
    • Hooks checkout y post-session: cuando la sesión tiene un repositorio en git gestionado por Anthropic, obtienen configuración http.sslVerify=false en lugar de la variable. También obtienen entradas http.<url>.sslVerify=true que mantienen las verificaciones activadas para git gestionado por Anthropic.
El archivo de certificado por sesión necesita un paquete de CA del sistema en /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.
Luego inicia un nuevo hilo en el canal y nombra el repositorio en tu primer mensaje. Si Claude sigue sin poder agregar el repositorio, consulta Si Claude no puede llegar a un repositorio para saber dónde revisar el vínculo de GitHub y la concesión del 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.com al comando del runner. El runner entonces reescribe https://github.com/owner/repo a git@github.com:owner/repo. SSH debe conectarse sin pedir nada.
La reescritura de host se ejecuta primero, así que enumere el nombre de host interno en --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 binario claude, 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:
Intercambia 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:
La sustitución de comandos busca el número de lanzamiento 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.
En una especificación de contenedor de Kubernetes, establezca esos valores iniciales con el siguiente bloque resources:
Las compilaciones y pruebas son generalmente la parte más grande y variable de la carga de una sesión, así que ejecute una compilación representativa de su repositorio, mida su CPU y memoria máximas, y aumente cualquier valor inicial que no deje espacio para el proceso de Claude Code en la parte superior de ese pico. El runner usa --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 hook spawn-runner envía, como la plantilla de pod de un Job de Kubernetes.
  • Varias sesiones por runner: en un --capacity por 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 4 sin 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.
    Da a cada runner su propio contenedor. Usa la misma ruta de --base-dir en 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.memory en Kubernetes, --memory en Docker o mem_limit en 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 MemoryMax en la unidad del runner y ejecuta un runner por host, porque los runners que usan el mismo --base-dir en un sistema de archivos comparten los repositorios clonados allí. Por ejemplo, en --capacity 1 sin un hook checkout, 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.
Cuando un contenedor o una unidad alcanza su límite de memoria, el kernel mata procesos solo dentro de él. Si estableces --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 devuelve EAGAIN, 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, y pids.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.
No inicies una sesión todavía, porque necesitas el directorio del siguiente paso antes de que comience la compilación.
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/pids seguido 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/cgroup seguido del texto que aparece después de 0::
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: lee pids.current una 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 --capacity del runner
  • Un límite que comparten varios runners: multiplica el pico por la suma de sus capacidades
Luego agrega el pico de cualquier otra cosa que comparta el límite, como otro contenedor en el Pod.
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 con dmesg, una línea que contenga cgroup: fork rejected by pids controller in y 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.max en el directorio de cgroup del runner y en cada directorio por encima de él. El valor más bajo es el límite vigente, y max significa que no hay límite en ese nivel.

Kubernetes

El runner sirve GET /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.
El Deployment anterior vive en un espacio de nombres claude-runners. Cree el espacio de nombres primero:
Crea el Secret de respaldo desde un archivo local que contenga el valor que copiaste en el paso Copiar clave de entorno de la UI de administración, para que el secreto nunca aparezca en tu historial del shell. Ejecuta (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 de SIGTERM, 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:
  1. El runner espera hasta --drain-wait-sec segundos, 0 por defecto, a que terminen los turnos que aún están en ejecución.
  2. 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ó.
  3. Ejecuta el hook de ciclo de vida post-session.
El runner sigue sondeando a Anthropic durante todo el drenaje. Eso mantiene sus sesiones asignadas a él, de modo que otro runner no tome una mientras tu hook post-session aún está guardando trabajo no confirmado. El tiempo registrado es la suma de estos valores: Con la configuración predeterminada, eso da 0 + 5 + 60 + 15 = 80 segundos. Un --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 ejemplo date +%s má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: terminationGracePeriodSeconds en Kubernetes, stop_grace_period en Docker Compose. Por ejemplo, con --drain-wait-sec 120, reemplaza el 90 en la receta de Kubernetes o de Docker Compose por 200.
  • 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-min establecido, usa el número mayor de la siguiente línea de inicio, después de Size the stop timeout to at least. Tiempo de apagado enumera lo que aumenta estos números.
Si el tiempo de parada se agota durante la espera, tu host mata al runner con trabajo aún en ejecución. Sus sesiones no obtienen ningún hook post-session. El runner tampoco se desregistra, así que el plano de control puede tardar unos minutos, y a veces más, en reencolarlas.

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 n minutos: el runner sirve sus sesiones normalmente y sigue aplicando --startup-timeout-min y --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 n minutos 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 n minutos se agotan y es 75 segundos en valores predeterminados. Si establece --drain-wait-sec por encima de 60 segundos, la gracia posterior a la liberación es --drain-wait-sec más 15 segundos en su lugar.
En cualquier etapa, el runner sale 0 tan pronto como no mantiene sesiones. Una segunda señal acorta las etapas: el runner drena inmediatamente, como lo hace en la primera señal sin --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: los n 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 hook post-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 SIGTERM mientras 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 segundo SIGTERM que recibe el runner. Nada señala un hook post-session en 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 con SIGPIPE, 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 el KillMode=control-group predeterminado 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 hook checkout en ejecución, que permanece adjunto al grupo deliberadamente, pero no un hook post-session en ejecución o el hijo de sesión.
  • Matanzas de cgroup, como el KillMode=control-group predeterminado de systemd o el SIGKILL que Kubernetes entrega a todo el contenedor cuando terminationGracePeriodSeconds expira: 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ía SIGTERM a todo el grupo de procesos del hook, luego SIGKILL dos 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 etapa SIGTERM está más allá del alcance del runner.
Cuando comienza el drenaje, y nuevamente en una salida forzada, el runner registra cuántos hooks 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 hook checkout, 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, desasocia HEAD y hace un reinicio duro a ella, lo cual es casi instantáneo cuando ha cambiado poco.
  • Con un --capacity mayor 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.
Proporciona el clon en la imagen o en un volumen persistente:
  • 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-dir a 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.
Lo que la ruta de reutilización hace y no garantiza:
  • Profundidad del clon: crea el clon sin --depth, con o sin --single-branch. El runner nunca pasa --depth cuando obtiene en un clon con un remoto origin, incluso si configuras CLAUDE_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 ejecuta git 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 hook checkout mientras 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-dir persistente, 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-state para 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 reftable pierde 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 refs reftable de git pierde así sus refs, por lo que debes precalentar el clon con el formato de refs files de 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-path o un hook command para 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=1 en 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 hook spawn-runner para 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, lee claude_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 1800 para 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-min para 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.
Para liberar las sesiones inactivas, inicia cada runner con --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.

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 de api.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/*
Si el tráfico de herramientas debe permanecer dentro de su red, ejecute las herramientas equivalentes como servidores MCP locales en la imagen del runner en su lugar. Consulte Servidores 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.
Una sesión liberada se reanuda desde un clon nuevo, por lo que el trabajo que no había empujado se ha ido de cualquier forma; consulte Las sesiones reanudadas pierden trabajo no empujado. Antes de v2.1.260, el runner terminaba cada sesión en el límite, después de esperar como máximo la ventana de gracia a que un turno en ejecución terminara. Establezca la bandera anterior a su sesión más larga esperada, como --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-release en 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 vida checkout no se les hace push. En su lugar, guarda una instantánea de ellos desde el hook post-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
  • 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 clone a 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 de HEAD a una rama de rescate desde un hook post-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 con claude 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.
Problemas comunes:
  • El runner no aparece en el entorno: confirme que el host pueda alcanzar api.anthropic.com sobre 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-dir a 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 ser self 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. Establece NODE_EXTRA_CA_CERTS en 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én GIT_SSL_CAINFO.
  • Tu script de inicio imprime un error de escritura que contiene Resource temporarily unavailable y se cierra: tu script comparte su stdout o stderr con el proceso claude self-hosted-runner u 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 con set -e el 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>&1 al 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. Establece NODE_EXTRA_CA_CERTS en 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 hook spawn-runner. Por ejemplo, una línea puede terminar en poll 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 que connected sea true en el cuerpo de /healthz del 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_account de cada runner o el campo locked_account de 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 reclamo act.email, que las sesiones de un agente Claude Tag nunca hacen. Sin el reclamo, el runner no emite ninguna serie locked_account y registra locked_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 con cannot create or write to base directory en esta lista.
  • Una sesión se recoge, pero Claude nunca responde su primer mensaje: si tus runners llegan a api.anthropic.com a través de un proxy, configúralo para que deje pasar la respuesta /worker/events/stream de 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 runner access denied by the git proxy, o un error de git que nombre una dirección de api.anthropic.com que 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-command o --proxy-authorization-file falla, agota el tiempo de espera después de 30 segundos o produce un valor vacío, el runner responde esa conexión con 502 Bad Gateway y 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 con could not start the proxy-authorization listener, no pudo abrir su oyente de loopback.
  • El runner registra líneas Poll failed que contienen rejecting 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 de api.anthropic.com sin alterarlas. El runner rechaza la respuesta malformada, la cuenta bajo el tipo transport de la métrica claude_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 remote o couldn't find remote ref: el runner interpreta el couldn't find remote ref de 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 imprimir couldn't find remote ref en 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 como CLAUDE_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-rewrite al 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 ref en su salida de error también se reportaban como una rama eliminada.
    Una vez que el runner esté actualizado o la causa esté corregida, reintenta la sesión o inicia una nueva. Si ninguna de estas comprobaciones encuentra la causa, reporta un problema. Para la fuente a la que la sesión envía resultados, una rama eliminada hace fallar la sesión. Para una fuente de git que la sesión solo lee, el runner omite esa fuente y continúa con las restantes. El runner hace fallar la sesión cuando omitirla la dejaría sin ningún repositorio. Antes de v2.1.228, una sesión así comenzaba en un directorio vacío.
  • 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 --version que el runner sea Claude Code v2.1.229 o posterior, ya que los runners más antiguos ignoran los hooks proporcionados; confirma que python3 --version funcione dentro de la imagen del runner, ya que los hooks ejecutan python3; y confirma que ningún archivo de configuración desactive los hooks, ya sea disableAllHooks en managed-settings.json, en el ~/.claude/settings.json del usuario del runner o en el .claude/settings.json o .claude/settings.local.json del repositorio, o bien allowManagedHooksOnly o un strictPluginOnlyCustomization que 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 source que 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 HTTP 403 aú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, reduce CLAUDE_RUNNER_FETCH_DEPTH desde 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étrica claude_code_self_hosted_runner_session_init_duration_seconds para 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_TOKEN nuevo 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ínea inference_token refresh failed que 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 terminationGracePeriodSeconds al menos al valor que el runner registra al inicio. Consulte Shutdown timing.
Una vez que se inicializa el registro, el runner escribe su registro de ciclo de vida, incluidas las líneas [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 contiene runner record gone server-side o, tras una interrupción más larga, poll auth failed. El runner no vuelve a registrarse por sí solo, así que reinícialo.
Configure su supervisor para reiniciar el runner cada vez que se cierre, para esperar más tiempo entre reinicios cuando el runner sigue cerrándose justo después de que comienza, y para notificar a alguien cuando eso sigue sucediendo. En producción, haz que tu supervisor reinicie el runner en un disco nuevo, en un contenedor o VM nuevos, como recomienda Reforzar tu despliegue.

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:
Busque la línea en la salida estándar y el error estándar del runner, en los registros del contenedor de su plataforma, o en el archivo que estableció con --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ínea error:, 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-git y 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 CrashLoopBackOff tambié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] o error: está entre las últimas líneas de la salida. Para leer la última ejecución de otro pod, nombre ese pod en lugar de deploy/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 RestartSec antes de cada reinicio y no lo alarga, así que una unidad con Restart=always reinicia 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 de systemctl reset-failed. Porque RestartSec se 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] o error:: 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-git y el proxy de git de Anthropic necesitan git en el PATH y un ~/.gitconfig escribible.
  • 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.
Para un diagnóstico guiado en el host del runner, ejecute el subcomando doctor.

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 a api.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.
Luego inicia una nueva sesión y comprueba que Claude responde. Aumentar los tiempos de espera del runner no ayuda: solo esperas más antes de que el runner detenga la sesión.

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.
  1. Espera a que transcurra --startup-timeout-min, 15 minutos por defecto. El runner entonces detiene la sesión y registra una línea que contiene no child output for y termina en — releasing.
  2. Busca SDKStartup: SSE en el registro del runner. Estas líneas siguen a una línea finished: 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.
  3. 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