Skip to main content
Los entornos autohospedados están en versión beta pública en planes Team y Enterprise; un Propietario los habilita activando Allow self-hosted environments en la página de administración Cloud environments. Esta página asume un ejecutor funcional; consulta el inicio rápido para la configuración e Implementar en producción para las recetas de flota.
Un entorno autohospedado ejecuta sesiones en la nube de Claude Code en tu propia infraestructura, ejecutadas por un proceso ejecutor que tú implementas. Sin configuración, ese ejecutor clona el repositorio de la sesión, genera Claude Code y limpia. Esta página es para el ingeniero de plataforma que opera los ejecutores: cubre los puntos de extensión para cuando esos valores predeterminados no se ajustan, desde el aprovisionamiento de credenciales por sesión hasta reemplazar completamente el checkout. Los contenedores y hooks se ejecutan como archivos ejecutables en el host del ejecutor, que es Linux o macOS, y los ejemplos en esta página asumen un shell POSIX. Algunas variables de entorno de hook en esta página aún usan pool, como CLAUDE_RUNNER_POOL_ID; los nombres de banderas CLI y variables de entorno usan environment, como --environment-secret-file.

Scripts contenedores

Usa un script contenedor cuando cada sesión necesite configuración que el ejecutor no puede hacer por sí solo: aprovisionamiento de credenciales de corta duración limitadas al creador de la sesión, exportación de secretos específicos del entorno, preparación de cadenas de herramientas de lenguaje o aplicación de límites de recursos alrededor del proceso secundario. El ejecutor inicia tu contenedor en lugar del binario de Claude Code, una vez por sesión. Termina el contenedor con exec en $CLAUDE_RUNNER_CLAUDE_BIN, el binario propio del ejecutor, para que las señales y los códigos de salida se propaguen correctamente. Apunta --exec-path, o SELF_HOSTED_RUNNER_EXEC_PATH, al contenedor cuando inicies el ejecutor:
El ejecutor establece lo siguiente en el entorno del contenedor: El contenedor también hereda el resto del entorno administrado del hijo, incluida cualquier variable de entorno proporcionada por el servidor. exec lo propaga todo automáticamente; si tu contenedor genera el hijo de otra manera, reenvía el entorno completo. Puedes leer las variables CLAUDE_CODE_REMOTE_SLACK_* en tu contenedor o hook command, y en lo que ejecuta la sesión, como comandos de shell, hooks de git y hooks de Claude Code. Los hooks checkout, post-session y spawn-runner no las reciben.

Dar un valor predeterminado a las variables que pueden no estar establecidas

Si tu script usa set -u, expande CCR_SESSION_ACCOUNT_EMAIL, CLAUDE_RUNNER_CLIENT_PLATFORM, CLAUDE_CODE_REMOTE_SLACK_THREAD_URL, CLAUDE_CODE_REMOTE_SLACK_THREAD_TS y CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID con un valor predeterminado, como "${CCR_SESSION_ACCOUNT_EMAIL:-}". Cada una puede no estar establecida, y con set -u Bash se detiene con unbound variable cuando expande una que no está establecida.

Poner entre comillas los argumentos y las variables que expande tu script

Pon entre comillas cada argumento y cada variable que el ejecutor pasa a tu contenedor o hook command. Algunos valores contienen caracteres que un shell interpreta, porque contienen JSON, oraciones completas o una regla de permisos como Bash(npm run test *).
  • Argumentos: pásalos como "$@"
  • Variables: pon cada una entre comillas, como en "${CLAUDE_CODE_REMOTE_SLACK_THREAD_URL:-}". El enlace del hilo de Slack, por ejemplo, puede contener ? y &.
  • Cadenas de eval y sh -c: no sustituyas un valor en una cadena que ejecuta eval o sh -c, ni siquiera entre comillas. En su lugar, haz que esa cadena haga referencia a la variable.

Mantener stdin y descriptor de archivo 3 adjuntos

El stdin del hijo es el canal de control del ejecutor. Las rotaciones de token y las señales de fin de sesión llegan por él. El ejecutor también abre una tubería en el descriptor de archivo 3 y lee de ella las señales de actividad del hijo para controlar los tiempos de espera de inactividad e inicio. Un simple exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" preserva ambos automáticamente. Si tu contenedor coloca el hijo en segundo plano con un simple &, corta el stdin del hijo. La sesión se ve saludable hasta que expira la vida útil de aproximadamente 30 minutos del token OAuth inicial, y luego cada llamada de API que usa el token falla con 401 authentication_error. Si tu contenedor debe colocar el hijo en segundo plano, por ejemplo para mantener viva una trampa de desmontaje, guarda stdin en el descriptor de archivo 9 y vuelve a adjuntarlo explícitamente:
Puedes redirigir el stdout del hijo. Mantén el descriptor de archivo 3 y stderr adjuntos al ejecutor:
  • Descriptor de archivo 3: lleva las señales de actividad del hijo al ejecutor. No lo cierres ni lo reutilices en el contenedor.
  • Descriptores de archivo 4 y 5: los ejecutores en Claude Code v2.1.309 o posterior los reservan para el hijo. No los cierres ni los reutilices en el contenedor. Si tu contenedor usa alguno de ellos, por ejemplo para guardar stdin, mueve ese uso a un descriptor del 6 al 9, que las versiones anteriores también dejan libres.
  • stderr: cuando el contenedor o el hijo salen con un código distinto de cero, el ejecutor publica las últimas líneas de stderr en la sesión y las imprime en su propio registro. El usuario de la sesión ve esas líneas, así que no imprimas secretos en stderr y quita set -x antes de implementar el contenedor. Si rediriges stderr, las sesiones siguen ejecutándose, pero el ejecutor informa un fallo solo con el código de salida.

Transmitir los flags del prompt del sistema

El prompt del sistema y el prompt del sistema anexado que el plano de control de Anthropic envía para una sesión llegan a tu contenedor como rutas de archivo, no como texto en línea. El ejecutor escribe cada prompt en un archivo en el directorio de configuración de la sesión, CLAUDE_CONFIG_DIR, y pasa su ruta en los argumentos que recibe tu contenedor, como --system-prompt-file <path> o --append-system-prompt-file <path>. Los ejecutores en Claude Code v2.1.281 o posterior entregan los prompts como archivos. Antes de v2.1.281, el ejecutor los pasaba como --system-prompt <text> y --append-system-prompt <text>. En tu script contenedor o hook command, maneja estos flags de la siguiente manera:
  • Transmítelos: termina el contenedor con exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@", que reenvía los flags de archivo junto con todos los demás argumentos. No los descartes ni los reescribas. Si una sesión pierde un flag de archivo de prompt, se ejecuta sin las instrucciones que el plano de control envió para ella.
  • En un ejecutor con v2.1.281 o posterior, un flag de archivo que agregues reemplaza el del servidor, nunca se suma a él: cada flag de archivo de prompt toma un único valor y Claude Code conserva la última aparición, por lo que si agregas --append-system-prompt-file <path> después de "$@", el contenido de tu archivo reemplaza las instrucciones anexadas del servidor. Para agregar instrucciones además de las del servidor, colócalas en el CLAUDE.md de la imagen del ejecutor, que el ejecutor incorpora en la configuración a nivel de usuario de cada sesión.

Corregir Argument list too long al inicio de la sesión

En Linux, si unes el prompt del plano de control y tu propio texto en un único argumento --append-system-prompt, el ejecutor registra una línea como esta una vez que el argumento alcanza 128 KB:
Linux limita cada argumento individual a 32 páginas de memoria, o 128 KB con las páginas habituales de 4 KB. La cifra de getconf ARG_MAX, a menudo 2 MB, es un límite distinto que se aplica a todos los argumentos y al entorno en conjunto. En Claude Code v2.1.283 o posterior, deja los flags de archivo en "$@" y pasa solo tu propio texto como --append-system-prompt. Claude Code usa ambos, primero el prompt del plano de control y luego tu propio texto:
En Linux, si tu propio texto alcanza 128 KB, muévelo al CLAUDE.md de la imagen del ejecutor, porque sigue siendo un único argumento. En v2.1.281 o v2.1.282, ese comando sale con Cannot use both --append-system-prompt and --append-system-prompt-file. En versiones anteriores, tu texto reemplaza el prompt del plano de control sin ningún error. En cualquiera de ellas, actualiza primero o mueve tu propio texto a CLAUDE.md.

Aprovisionar credenciales limitadas al creador de la sesión

Usa el subcomando decode-token para leer reclamaciones del JWT de sesión. Lee el token de un argumento, de CLAUDE_CODE_SESSION_ACCESS_TOKEN o de stdin, en ese orden; consulta Verificar el token dentro de la sesión para saber qué verifica. El ejemplo a continuación decodifica la identidad del creador, la intercambia por credenciales de AWS de corta duración y hace exec a Claude Code:
Usa jq -re en lugar de jq -r cuando la reclamación extraída controla una decisión de autenticación, para que una reclamación ausente salga con código distinto de cero en lugar de pasar la cadena literal null aguas abajo. Las sesiones creadas por una identidad de servicio de la organización, como sesiones de bot y de agente, llevan un asunto agent: en lugar de user:, por lo que este ejemplo las rechaza; si tu entorno sirve esas sesiones, decide explícitamente si el contenedor recurre a una credencial predeterminada para ellas en lugar de salir. Cuando tu intercambio de credenciales necesite el correo electrónico en su lugar, lee .act.email y maneja su ausencia: el token lo lleva solo cuando la superficie de creación lo registró, y una sesión despachada por CLI puede carecer de él. Para la referencia completa de reclamaciones y la verificación desde servicios fuera del ejecutor, consulta Verificar identidad de sesión.

Hooks de ciclo de vida

Los hooks de ciclo de vida reemplazan etapas del pipeline por sesión del ejecutor con sus propios scripts. Apunte el ejecutor a un directorio de hooks con --hooks-dir <path>, o SELF_HOSTED_RUNNER_HOOKS_DIR. El ejecutor busca archivos ejecutables con nombres bien conocidos; cualquier hook que no esté presente cae en el comportamiento integrado, por lo que solo escribe los que necesita. Los hooks se ejecutan con los privilegios propios del ejecutor, y los hijos de sesión comparten ese UID, por lo que monte el directorio de hooks como solo lectura, o incorpórelo en la imagen, para que el código de sesión no pueda modificarlo; consulte la sección de endurecimiento. Estos hooks son distintos de los hooks de Claude Code, que se ejecutan dentro de la sesión; los hooks de ciclo de vida se ejecutan en el ejecutor, alrededor de la sesión.

checkout

Se ejecuta una vez por repositorio, en lugar del clon y fetch integrados del ejecutor. Usa el hook para clonar desde un espejo de lectura al que accedes por HTTPS o SSH, sembrar una copia de trabajo desde un archivo, o aplicar autenticación git por sesión. El ejecutor establece estas variables, y puede establecer otras variables CLAUDE_RUNNER_ que la tabla no enumera: El script debe dejar una copia de trabajo en CLAUDE_RUNNER_CHECKOUT_PATH extraída en la revisión solicitada. Un HEAD desacoplado funciona, porque el ejecutor crea la rama de trabajo de la sesión encima. Después de que tu hook regresa, el ejecutor verifica que CLAUDE_RUNNER_CHECKOUT_PATH contenga un .git. Si tu hook materializa una fuente no git como Perforce o un tarball desempaquetado, establece CLAUDE_RUNNER_SKIP_GIT_VERIFY=1 en el entorno del ejecutor para omitir esa verificación. Los flujos basados en git como la creación de la rama de trabajo y el envío de resultados requieren un checkout de git, así que exporta los resultados de copias no git con un hook post-session.

Obtener credenciales de git en el hook

El ejecutor no pasa una credencial git al hook. El subcomando decode-token tampoco está disponible aquí, porque CLAUDE_RUNNER_CLAUDE_BIN no se establece en el entorno del hook de checkout. En su lugar, genera una credencial de clon por sesión a partir de la identidad de la sesión, o recurre a la autenticación git propia del host:
  • Credencial de clon por sesión: verifica CLAUDE_CODE_SESSION_ACCESS_TOKEN con una biblioteca JWT estándar contra el endpoint JWKS bajo CLAUDE_RUNNER_API_BASE_URL, como se describe en Verificar el token desde tu servicio. Luego haz que tu servicio de credenciales emita una credencial de clon de corta duración para la identidad en la reclamación act del token. Asocia esa credencial a act.sub, y no exijas act.email.
  • Autenticación git del host: usa la autenticación git que el host ya tenga, como un agente SSH, un ayudante de credenciales o .netrc.

Cuando el hook falla

El hook falla cuando sale con código distinto de cero, o sale con 0 sin dejar un checkout utilizable:
  • Un repositorio al que la sesión envía resultados: el ejecutor falla la sesión, y en una salida distinta de cero muestra la cola del stderr del script al usuario. Escribe el motivo en stderr como una sola línea breve, escríbelo al final y luego sal con un código distinto de cero. No imprimas secretos en stderr, y quita set -x antes de desplegar el hook. Si el mensaje couldn't find remote ref de git aparece aproximadamente en los últimos 4 KB del stderr del hook, el ejecutor informa en su lugar de una rama eliminada. El mensaje de error de la sesión indica entonces que la rama ya no existe en el remoto y pide al usuario que la restaure. Si esa rama existe, consulta Una sesión falla con no longer exists on the remote o couldn't find remote ref.
  • Un repositorio que la sesión solo lee, como un repositorio agregado a una sesión en ejecución: el ejecutor lo omite y continúa con los repositorios restantes. El ejecutor publica un paso Skipped en la sesión y elimina lo que el hook haya dejado en la ruta de checkout. Cuando el paso Skipped menciona el hook de checkout, busca el detalle del fallo en la línea [runner:warn] checkout hook failed del registro del ejecutor. Cuando el paso indica que una ref de origen no longer exists on remote, consulta Una sesión falla con no longer exists on the remote o couldn't find remote ref. Si al omitirlo la sesión queda sin ningún repositorio, el ejecutor falla la sesión de todas formas.
Cuando el hook tiene éxito, el ejecutor elimina la ruta de checkout después de que la sesión termina.

post-session

Se ejecuta una vez por sesión, después de que el hijo de Claude Code ha salido y antes de que el ejecutor desmonte el espacio de trabajo. Este hook es su única oportunidad para guardar trabajo no confirmado: en --capacity por encima de uno, el ejecutor elimina árboles de trabajo por sesión justo después de que el hook regresa, y en --capacity 1 el clon canónico reutilizado se reinicia cuando la siguiente sesión comienza, por lo que los cambios rastreados no confirmados no sobreviven en ninguna ruta. Los usos típicos son enviar una rama de instantánea de cambios no confirmados, archivar registros o emitir un evento de fin de sesión a sus propios sistemas. El hook se dispara en cada fin de sesión donde se generó un proceso hijo, sea cual sea la causa; los valores CLAUDE_RUNNER_EXIT_REASON a continuación enumeran los casos. No puede dispararse cuando el ejecutor termina abruptamente, como una preferencia de VM o una pérdida de energía; si necesita garantías contra terminación abrupta, tome instantáneas periódicamente desde dentro de la sesión con un hook PostToolUse de Claude Code en su lugar. El ejecutor establece: CLAUDE_RUNNER_EXIT_REASON toma uno de cuatro valores:
  • completed: la sesión terminó limpiamente. El proceso de Claude Code salió normalmente, o salió por sí mismo después de que la sesión fue archivada o eliminada.
  • failed: el proceso de Claude Code se bloqueó, o la configuración falló después de que comenzó.
  • interrupted: el ejecutor detuvo la sesión, en uno de estos casos:
    • El ejecutor liberó la sesión para liberar la ranura.
    • Se agotó el tiempo de espera de la sesión al inicio.
    • El servidor movió la sesión fuera de este ejecutor.
    • El sondeo del ejecutor detectó un archivado o una eliminación antes de que el proceso saliera.
    • El ejecutor estaba drenando.
    • La sesión superó su límite de --kill-session-after-min.
  • abandoned: reservado para una sesión que otro ejecutor reclamó. El hook actualmente no se dispara en ese caso.
Si comparas los recibos del hook con los contadores de ciclo de vida de sesión, espera que algunos recibos interrupted cuenten allí como completed. Los contadores cuentan como completed una liberación, un tiempo de espera agotado al inicio, un movimiento de servidor y un archivado o eliminación que el sondeo del ejecutor detectó primero, porque el ejecutor devolvió la ranura limpiamente. El estado de salida del hook nunca afecta el resultado de la sesión; un fallo se registra e ignora. El ejecutor espera hasta --post-session-hook-timeout-sec, 60 segundos por defecto, en cada fin de sesión incluido el apagado del ejecutor. Este ejemplo guarda trabajo no confirmado en una rama de rescate:
La línea GIT_ALLOW_PROTOCOL del script limita git a remotos HTTPS, HTTP y SSH. Si el entorno del ejecutor ya establece su propia lista GIT_ALLOW_PROTOCOL no vacía, el script conserva esa lista. El hook hace push con las credenciales git que estén disponibles en su propio entorno en el host del ejecutor. Con la postura de no credenciales en la imagen, incluido cuando el clon integrado pasa por el proxy git de Anthropic, no hay ninguna, así que genera una credencial de push de corta duración dentro del hook antes de hacer push: intercambia el token de sesión que el hook recibe en CLAUDE_CODE_SESSION_ACCESS_TOKEN con tu propio servicio de tokens, verificándolo como se describe en Verificar la identidad de sesión. Trata cualquier credencial que tu hook le dé a git como una que una sesión puede obtener, y genérala de modo que no pueda hacer más que este push. Git en tu hook lee archivos de configuración que una sesión puede escribir, y un ayudante de credenciales o un controlador de filtros nombrado en uno de ellos se ejecuta con los privilegios de tu hook. Los ajustes de esos archivos también pueden cambiar el destino de un push, sea cual sea el remoto que nombres. Para conocer los ajustes de git que el ejecutor fija en tu hook y los que deja en manos de esos archivos, consulta Configuración de Git dentro de los hooks de ciclo de vida.

Tiempo del hook cuando el ejecutor libera una sesión

Una sesión liberada puede reanudarse en otro ejecutor. En un ejecutor en v2.1.236 o posterior, lo que la sesión estaba haciendo en la liberación decide si puede reanudarse antes de que este hook termine:
  • Inactivo después de un turno, o tiempo de espera agotado al inicio: el ejecutor detiene el hijo y ejecuta este hook hasta completarse. Solo entonces libera la sesión. Un mensaje de usuario enviado mientras se ejecuta el hook no puede reanudar la sesión en otro ejecutor antes de que el hook termine.
  • Esperando que el usuario responda a un mensaje, como un mensaje de permiso: el ejecutor libera la sesión primero, luego ejecuta este hook. Un mensaje de usuario enviado mientras se ejecuta el hook puede reanudar la sesión en otro ejecutor antes de que el hook termine.
Esto se aplica siempre que el ejecutor libera una sesión: en el tiempo de espera inactivo, en el tiempo --retire-at, y, en un ejecutor en v2.1.260 o posterior, en el límite --kill-session-after-min de una sesión. Una sesión cuyo turno ha terminado y que solo contiene tareas de fondo cuenta como inactiva aquí. Antes de v2.1.236, el ejecutor liberaba la sesión primero y luego ejecutaba este hook en ambos casos. Durante un drenaje SIGTERM, el ejecutor mantiene el arrendamiento de sesión hasta que el hook termina; consulte Tiempo de apagado.

Configuración de Git dentro de los hooks de ciclo de vida

Los hooks checkout y post-session se ejecutan con el token de acceso de la sesión en su entorno, y el git que ejecutan lee archivos de configuración que las sesiones pueden escribir, como ~/.gitconfig y el .git/config de un checkout. Antes de que se ejecute cualquiera de los dos hooks, el ejecutor establece ajustes de git en el entorno del hook, incluidos los que se indican a continuación, como pares GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n y variables de entorno de git. Git les da prioridad sobre todos los archivos de configuración, y solo se aplican al git que ejecutan tus hooks, no al git propio de la sesión. Al iniciar, el ejecutor imprime una línea [runner:git] lifecycle hooks: que muestra la ruta de hooks, los protocolos permitidos, los programas gpg y el modo de firma vigentes. Requiere Claude Code v2.1.280 o posterior.
  • Hooks de Git: a menos que proporciones un valor, core.hooksPath es /dev/null, por lo que git omite los hooks en el .git/hooks de un repositorio y cualquier directorio de hooks que nombre ~/.gitconfig. Para proporcionar uno, exporta core.hooksPath como un par GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n en el entorno del ejecutor. El ejecutor también lee core.hooksPath de la configuración de git del sistema, y solo lo usa cuando el usuario del ejecutor no puede escribir en ese archivo, en el directorio que nombra ni en los archivos de hooks que contiene. Cuando el ejecutor ignora un valor, una línea [runner:warn] al iniciar indica el valor y el motivo.
  • Monitor del sistema de archivos: core.fsmonitor está vacío, por lo que git en tu hook no ejecuta un programa de monitor que nombre un archivo de configuración.
  • Protocolos remotos: GIT_ALLOW_PROTOCOL es https:http:ssh. Un clone, fetch o push que use una ruta local, una URL file:// o una URL git:// falla con fatal: transport 'file' not allowed o fatal: transport 'git' not allowed.
  • Comando SSH y solicitud de credenciales: git en tu hook ignora core.sshCommand y core.askPass de los archivos de configuración. Para usar tu propio comando SSH, establece GIT_SSH_COMMAND en el entorno del ejecutor. Para usar un programa de solicitud de credenciales, establece allí GIT_ASKPASS. Las sesiones heredan el entorno del ejecutor, por lo que ambas variables también llegan al git propio de la sesión. No pongas una credencial en ninguna de ellas.
  • Programas gpg: gpg.program, gpg.openpgp.program, gpg.x509.program y gpg.ssh.program son rutas que establece el ejecutor, nunca valores de un archivo de configuración.
  • Firma de commits: con --configure-git, los commits que hagas desde un hook se firman como la sesión. Sin el flag, commit.gpgsign y tag.gpgsign son false.
Para cambiar uno de estos ajustes, usa el entorno del ejecutor o una opción git -c dentro del hook:
  • Pares de configuración: un par GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n que exportes en el entorno del ejecutor reemplaza el valor del ejecutor para la misma clave. Numera tus pares a partir de 0 y establece GIT_CONFIG_COUNT en la cantidad que haya. Cuando falta el último par que anuncia el conteo, el ejecutor ignora todos tus pares y registra una línea [runner:warn] al iniciar.
  • Variables de entorno de Git: el ejecutor deja GIT_ALLOW_PROTOCOL, GIT_SSH_COMMAND y GIT_ASKPASS tal como los establezcas en su entorno.
  • Opciones git -c: una opción git -c dentro del hook sobrescribe un par GIT_CONFIG_KEY_n, sea del ejecutor o tuyo. No cambia GIT_ALLOW_PROTOCOL, GIT_SSH_COMMAND ni GIT_ASKPASS, que git lee antes que cualquier configuración.
Git en tu hook sigue leyendo todos los ajustes que el ejecutor no establece, como los ayudantes de credenciales, las reescrituras url.*.insteadOf y los controladores de filtros, de todos los archivos de configuración, incluidos los que las sesiones pueden escribir. Un ayudante de credenciales o un controlador de filtros nombrado en uno de esos archivos se ejecuta como un programa con los privilegios de tu hook, y la configuración en esos archivos aún puede cambiar el destino de un push desde tu hook, incluido un push a una URL que pases en la línea de comandos. Antes de v2.1.280, el ejecutor no establecía ninguno de estos ajustes, y con --configure-git un commit hecho desde un hook fallaba a menos que el hook pasara -c commit.gpgsign=false.

command

Se ejecuta una vez por sesión después del checkout, en lugar de la generación de hijo integrada. El hook recibe el mismo entorno que un script contenedor y debe exec en "$CLAUDE_RUNNER_CLAUDE_BIN" de la misma manera. Use el hook command para mantener toda la personalización en un directorio de hooks; use --exec-path cuando el contenedor vive en otro lugar. Si --exec-path también se establece, la bandera tiene precedencia y el hook command se ignora. Siempre exec el binario propio del ejecutor en lugar de un claude resuelto por PATH; de lo contrario, anula el fijación de versión.

Ejecutores bajo demanda

En lugar de ejecutar una flota fija, puede arrancar un ejecutor por sesión. El orquestador es un subcomando separado y sin estado que sondea a Anthropic para solicitudes de generación, una por sesión que está en cola sin ejecutor disponible, y ejecuta su hook spawn-runner para cada una. Su hook envía una carga de trabajo a su plataforma: un Job de Kubernetes, una instancia de EC2, un despacho de Nomad. Los ejecutores bajo demanda mejoran la higiene de credenciales. En una flota fija, el secreto del entorno vive en cada host del ejecutor, que es el mismo host que ejecuta sesiones de usuario. Con el orquestador, el secreto del entorno permanece solo en el host del orquestador, que nunca ejecuta código de usuario; cada ejecutor generado recibe una orden de trabajo de un solo uso que registra exactamente un ejecutor y luego expira. Para iniciar el orquestador, pase el secreto del entorno y un directorio de hooks que contenga un script spawn-runner ejecutable:
El orquestador no mantiene estado entre sondeos, por lo que puede ejecutar dos o más réplicas contra el mismo entorno para disponibilidad. Cada solicitud de generación es reclamada del lado del servidor por exactamente una réplica. Todas las réplicas deben usar el mismo valor --expected-spawn-seconds; consulte el contrato del hook.

El hook spawn-runner

El orquestador ejecuta ${hooks-dir}/spawn-runner una vez por solicitud de generación. El hook debe enviar trabajo de forma asincrónica, sin esperar a que el ejecutor arranque, y regresar dentro de --hook-timeout, 60 segundos por defecto. El hook recibe: El ejecutor generado se registra con la orden de trabajo en lugar del secreto del entorno:
  • Inicie con la orden de trabajo: apunte --environment-secret-file a un archivo que contenga el JWT de orden de trabajo, o establezca SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET al valor JWT.
  • Copie el JWT antes de que el hook salga: el orquestador elimina el archivo de orden de trabajo después de que el hook sale, por lo que copie el JWT en la carga de trabajo que envía, como un Secret de Kubernetes en el Job generado, en lugar de pasar la ruta del archivo.
  • Use --capacity 1 en ejecutores generados: una orden de trabajo vinculada a sesión registra exactamente un ejecutor vinculado a esa sesión, por lo que una capacidad más alta agrega espacios que nunca reciben trabajo, y el ejecutor registra una advertencia al inicio.
  • Las órdenes de trabajo de precalentamiento registran sin vincular: el ejecutor en espera no está vinculado a una sesión y reclama trabajo en cola como un ejecutor de flota fija.
El contrato tiene cuatro reglas, sea cual sea la plataforma en la que tu hook aprovisiona:
  1. Sé idempotente en CLAUDE_RUNNER_ORDER_ID. La reentrega de la misma solicitud debe generar como máximo un ejecutor. Deriva un nombre de recurso determinista del ID de orden y deja que tu plataforma rechace el duplicado. No uses CLAUDE_RUNNER_SESSION_ID como clave en su lugar. Cada resolicitud de una sesión lleva el mismo ID de sesión con un nuevo ID de orden. Una carga de trabajo nombrada o deduplicada por el ID de sesión no se vuelve a crear mientras exista la primera.
  2. No reintente la carga de trabajo. Un ID de orden significa como máximo una carga de trabajo creada. Si el ejecutor nunca se registra, Anthropic reintenta con un ID de orden nuevo después de --expected-spawn-seconds.
  3. Usa el contrato de código de salida. Sal con el estado que corresponda al resultado:
    • Salida 0: enviado.
    • Salida 1, o si sigue en ejecución cuando termina --hook-timeout: fallo reintentable. La sesión espera un tiempo y se vuelve a ofrecer. Después de 10 solicitudes fallidas seguidas, queda bloqueada igual que tras una salida 2 o superior.
    • Salida 2 o superior: fallo no reintentable. La sesión queda bloqueada para generar nuevamente hasta que un usuario le envíe un mensaje nuevo o un Propietario seleccione Retry en ella en la pestaña Activity del entorno.
    Mientras una sesión espera para reintentar, aparece como Queued en la pestaña Activity sin error de generación, así que lee la salida del hook en el registro del orquestador. Una vez bloqueada, la sesión aparece como Failed. Expándela para leer el error de generación, tomado de los últimos 512 caracteres del stderr del hook. Escribe un error procesable en stderr, y ningún secreto. En un hook de shell, mantén los fallos transitorios reintentables. Una solicitud de precalentamiento no tiene sesión que pueda fallar: el orquestador registra una salida distinta de cero solo localmente, y el servidor vuelve a solicitar la generación después de que expira el arrendamiento de --expected-spawn-seconds.
  4. Establece --expected-spawn-seconds en al menos tu tiempo p99 desde la solicitud de generación hasta el registro del ejecutor. Mide desde el momento en que el orquestador recibe la solicitud de generación, e incluye cualquier espera de capacidad en tu plataforma además del tiempo de arranque. Este valor es el arrendamiento del lado del servidor, y la orden de trabajo expira con él, por lo que un ejecutor cuya carga de trabajo tarde más no puede registrarse. Todas las réplicas del orquestador deben usar el mismo valor.
Todo lo que el hook escribe a stdout o stderr aparece en el registro del orquestador con credenciales automáticamente redactadas. Si las sesiones permanecen en cola, verifique el cuerpo /healthz del orquestador para contar colas, luego abra la pestaña Activity de su entorno en la página de administración Cloud environments: expanda una sesión fallida allí para su error de generación, y seleccione Retry para reintentarla.

Corrige las sesiones reabiertas que permanecen en Queued o muestran Failed

Si las sesiones reabiertas permanecen en Queued o muestran Failed, o tu hook agota el tiempo de espera con ellas, una causa es un hook que nombra o deduplica las cargas de trabajo por el ID de sesión. Una sesión se reabre cuando su usuario le envía un mensaje después de que su ejecutor ha salido. Por ejemplo, toma un hook que construye el nombre de cada carga de trabajo así:
  1. En la primera solicitud de generación de la sesión, el hook crea una carga de trabajo con ese nombre, y se inicia un ejecutor
  2. El ejecutor sale, por ejemplo después de --release-idle-session-min, y el usuario reabre la sesión más tarde
  3. La siguiente solicitud de generación de la sesión lleva el mismo ID de sesión y un nuevo ID de orden, así que el hook construye el mismo nombre
  4. Si tu plataforma todavía tiene la carga de trabajo de la primera solicitud, porque nada la eliminó cuando su ejecutor salió, el hook no crea ninguna carga de trabajo nueva y no se inicia ningún ejecutor
  5. Cada solicitud de generación posterior para la sesión termina de la misma manera
Abre tu propio hook y lee qué hace cuando la carga de trabajo ya existe, por ejemplo cuando tu plataforma rechaza el nombre duplicado. Busca ese comportamiento en la primera columna para ver qué muestra la sesión reabierta en la pestaña Activity: En el error de generación de la última fila, 60000 es tu --hook-timeout en milisegundos. Cuando un hook espera hasta que termina --hook-timeout, el registro del orquestador también recibe una línea timed out after. Para corregir el hook, construye cada nombre o clave a partir de CLAUDE_RUNNER_ORDER_ID, como exige la regla 1 del contrato del hook. En el ejemplo, el nombre pasa a ser:
Si tu hook espera, haz también que salga una vez que haya enviado la carga de trabajo, porque un hook que espera retrasa los siguientes hooks de su orquestador. Después de cambiar el hook, comprueba el estado de cada sesión atascada en la pestaña Activity:
  • Queued: la sesión recibe su siguiente solicitud de generación por sí sola
  • Failed: selecciona Retry en ella como Propietario, o haz que su usuario le envíe un mensaje nuevo

Haz que los ejecutores bajo demanda salgan cuando estén inactivos

De forma predeterminada, un ejecutor que inicia tu hook no sale cuando termina el turno de su sesión, por lo que la carga de trabajo que enviaste sigue en ejecución. Para que los ejecutores salgan, haz que tu hook pase estos flags del ejecutor: Elimina la carga de trabajo cuando el ejecutor salga, por ejemplo, estableciendo ttlSecondsAfterFinished en un Job de Kubernetes o haciendo que el script de inicio de la VM elimine la VM después.

Envía la salida de un proceso en segundo plano a un archivo

Si tu hook spawn-runner deja un proceso ejecutándose en segundo plano, envía la salida de ese proceso a un archivo que el usuario del hook pueda crear. Por ejemplo, agrega >> "$HOME/claude-runner.log" 2>&1 al comando que inicia el proceso, antes de cualquier &. Esta línea del hook muestra dónde va la redirección, con your-background-command en lugar del comando que tu hook ejecuta en segundo plano:
Sin la redirección, el proceso escribe en el stdout y el stderr del hook:
  • Aproximadamente un segundo después de que el hook sale, el orquestador registra exited, but a process it started still has the hook's stdout or stderr open. A partir de esa línea, registra hasta 64 KB más de esa salida bajo [runner:hook:spawn-runner:after-exit jti=<order id>] y luego descarta el resto.
  • Una vez que el orquestador sale, las escrituras del proceso allí fallan.
Un ejecutor que tu hook inicia por sí mismo ejecuta sesiones en el host del orquestador, que contiene el secreto del entorno. Antes de la v2.1.305, un hook que ya había salido seguía alcanzando --hook-timeout mientras un proceso que había iniciado tuviera abierto el stdout o el stderr del hook, lo que se mostraba como:
  • Pestaña Activity, en el error de generación de una sesión fallida: spawn-runner hook timed out after 60000ms, o las últimas líneas del stderr del hook.
  • Registro del orquestador: timed out after 60000ms, sending SIGTERM to process tree.

Sal del hook sin esperar al ejecutor

Si cada ejecutor bajo demanda se detiene unos 60 segundos después de que se inicia su hook, y tu hook se ejecuta de nuevo para la misma sesión, comprueba si el hook espera al ejecutor. Los 60 segundos son el --hook-timeout predeterminado, así que si estableces el tuyo, los ejecutores se detienen ese tiempo después de que se inicia el hook. Un hook que inicia claude self-hosted-runner en el host del orquestador y lo espera, en primer plano o con wait, sigue en ejecución cuando termina --hook-timeout. El orquestador entonces detiene el hook y los procesos que se ejecutan bajo él, incluidos ese ejecutor y el proceso de Claude de la sesión, por lo que un turno que aún está en ejecución se interrumpe.
  • Para confirmar que el tiempo de espera es lo que detiene tus ejecutores: busca en el registro del orquestador timed out after 60000ms, sending SIGTERM to process tree en cada solicitud de generación. El 60000 es tu --hook-timeout en milisegundos.
  • Para corregirlo: haz que el hook envíe el ejecutor a tu plataforma y salga con 0. Si en su lugar aumentas --hook-timeout, el ejecutor solo se detiene más tarde.

Mantén los fallos transitorios reintentables en un hook de shell

En un hook de shell que usa set -e, un fallo que un reintento podría haber resuelto puede bloquear la sesión. El hook se detiene en el comando que falla y sale con el estado propio de ese comando, y el orquestador aplica el contrato de código de salida a ese estado. Muchos fallos devuelven un estado de 2 o superior, como 127 cuando un comando no está instalado y 22 de curl --fail ante un error HTTP, por lo que bloquean la sesión en su primer fallo. Una sesión que el hook ya bloqueó permanece bloqueada hasta que un usuario le envíe un mensaje nuevo o un Propietario seleccione Retry en ella en la pestaña Activity del entorno. Para convertir ese tipo de fallo en una salida 1, coloca estas líneas justo debajo de la línea #! del hook, por encima de cualquier cosa que pueda fallar:
Estas líneas cambian cómo se comporta el resto del hook, así que revísalo en busca de cada uno de estos patrones después de agregarlas:
  • exit 2 o superior a secas: con el trap establecido, se convierte en salida 1. Para un error que ningún reintento puede corregir, llama a permanent con el motivo en su lugar, como permanent "namespace claude-runners does not exist". Llámalo en el shell principal, no dentro de $( ), ( ) ni de una tubería.
  • exec: no inicies el último comando del hook con exec, porque exec reemplaza el shell y el trap no se ejecuta.
  • Segundo trap EXIT: un segundo trap ... EXIT reemplaza al primero, así que combina los dos en un solo trap. Coloca tus comandos de limpieza justo después de rc=$?; y termina cada uno con || true;. Así la limpieza se ejecuta tanto en caso de fallo como de éxito, y un comando de limpieza que falla no establece el estado de salida del hook. Este trap combinado muestra la forma, con your-cleanup-command en lugar de tu propio comando:
  • Comandos a los que se les permite fallar: si el hook no usaba set -e antes, ahora se detiene en el primer comando que devuelve un valor distinto de cero, como una búsqueda que no encuentra nada. Si el hook actúa según el resultado, convierte ese comando en la condición de un if. Si ignora el resultado, añade || true después del comando.
  • El comando que envía la carga de trabajo: no le añadas || true después. Con || true, el hook sale con 0 incluso cuando tu plataforma rechaza el envío, por ejemplo por exceder la cuota, y la sesión espera a un ejecutor que nunca se inició. Nombra la carga de trabajo según CLAUDE_RUNNER_ORDER_ID, como indica la primera regla del hook. Así, una respuesta de que ya existe es una reentrega de la misma solicitud, por lo que solo esa respuesta debe tratarse como éxito. Para cualquier otra, imprime la respuesta en stderr y sal con 1, o llama a permanent si ningún reintento puede corregirlo. Este ejemplo lo hace para kubectl create, con un job.yaml que contiene solo la carga de trabajo:
    El registro del orquestador muestra entonces ese mensaje. Si el hook crea otros objetos, nombra cada uno según el ID de orden, dale su propio comando y trata su respuesta de que ya existe como éxito de la misma manera. Para un objeto que contiene un secreto, imprime un mensaje fijo en lugar de la salida del comando.
Para confirmar que el trap funciona, agrega justo debajo de la línea trap una línea que llame a un comando que no existe, como no-such-command. Ejecuta el archivo del hook desde tu shell y comprueba que echo $? imprima 1; luego elimina la línea.

Enviar solicitudes de modelo a Bedrock o Agent Platform

Si tu organización necesita que las solicitudes de modelo pasen por su propia cuenta de AWS o Google Cloud, configura el runner para Amazon Bedrock o Agent Platform de Google Cloud, antes Vertex AI. Cada sesión que inicie ese runner llamará entonces al modelo en tu cuenta de nube, con tus credenciales de nube. Sin esta configuración, las sesiones envían las solicitudes de modelo a la API de Anthropic. El runner sigue consultando a Anthropic en busca de sesiones, y cada sesión sigue enviando su flujo de eventos a api.anthropic.com. El flujo de eventos transporta prompts, respuestas y resultados de herramientas. El requisito de plan y la exclusión de Zero Data Retention de Disponibilidad y limitaciones siguen aplicándose. Las sesiones se enrutan a un entorno, no a un runner, y una sesión que se vuelve a poner en cola o se reanuda puede ejecutarse en un runner distinto. Configura todos los runners del entorno de la misma manera. Antes de empezar, lee qué cambia con estos proveedores.
1

Prepara la cuenta de nube y tus reglas de salida

Configura el acceso al modelo, una política o un rol de alcance limitado y el acceso de red:
  • Amazon Bedrock: envía los detalles del caso de uso y luego crea la política de Configuración de IAM, limitando bedrock:InvokeModel y bedrock:InvokeModelWithResponseStream a los perfiles de inferencia que usan tus sesiones y a los modelos base que hay detrás de ellos
  • Agent Platform: habilita la API y solicita acceso al modelo, luego crea el rol personalizado que describe Configuración de IAM, con solo aiplatform.endpoints.predict
  • Salida de red: permite los endpoints de tu proveedor en tus reglas de salida. Consulta Requisitos de red. Si las sesiones no pueden alcanzarlos, Claude Code puede seguir reintentando durante horas antes de que la sesión muestre un error.
2

Dale a las sesiones credenciales de alcance limitado

Asocia la política o el rol del paso 1 a una identidad que no pueda hacer nada más. Para conocer los métodos que acepta Claude Code, consulta Configurar credenciales de AWS y Configurar credenciales de GCP.
Cualquiera que consiga ejecutar código en una sesión, incluso mediante inyección de prompts, puede usar estas credenciales a tu costa mientras las credenciales sean válidas. Claude Code se ejecuta dentro de la sesión, por lo que la credencial con la que llama al modelo tiene que poder leerse allí.Los comandos de shell que ejecuta Claude, tus hooks de Claude Code y los servidores MCP stdio heredan el entorno de la sesión y se ejecutan como el mismo usuario que Claude Code. Como resultado, pueden leer las variables de credenciales y los archivos de credenciales.Cuando refuerzas tu despliegue, mantienes las credenciales del host fuera de las sesiones, pero no puedes dejar fuera esta credencial. No le des a la identidad que hay detrás nada más allá de la política o el rol del paso 1.
Comprueba el método que elijas frente a estos comportamientos del runner:
  • Endpoint de metadatos: si les niegas por completo a las sesiones el endpoint de metadatos de la nube, las credenciales que se sirven desde él, como un perfil de instancia, tampoco llegan a Claude Code. Una identidad web basada en archivos, como IAM Roles for Service Accounts (IRSA) en Amazon EKS o un archivo de credenciales de Workload Identity Federation, no depende de él.
  • Renovación: una sesión puede durar más que una credencial, así que usa un método que se renueve solo, como una identidad web basada en archivos
  • Script envoltorio: el runner inicia tu script envoltorio una vez por sesión, por lo que las credenciales que exporta no se renuevan. Claude Code lee las credenciales de AWS de su entorno, así que si tu script envoltorio ya exporta credenciales de AWS para otro trabajo, Claude Code puede firmar las solicitudes de modelo con ellas.
3

Establece las variables de un solo proveedor en el entorno del runner

Establece las variables de exactamente un proveedor donde configuras las demás variables de entorno del runner, como la especificación del contenedor o la unidad de servicio, y luego reinicia el runner. Los ejemplos las muestran como exports de shell. Con runners bajo demanda, establécelas en la carga de trabajo que inicia tu hook spawn-runner.Inicia estos runners con --confine-repo-settings enforce. Este rechaza sesiones en repositorios cuya configuración confirmada marca, así que ejecuta primero en el modo predeterminado warn y resuelve lo que registra.
Reemplaza la región por la tuya:
Para saber cómo resuelve Claude Code la región, consulta Configurar Claude Code. Para saber qué prefijo de perfil de inferencia usa Claude Code para tu región, consulta Prefijos de perfiles de inferencia entre regiones.
4

Comprueba que las variables llegaron a una sesión

Tu propio shell en el host es un proceso distinto, así que comprueba desde dentro de una sesión. Inicia una en el entorno y pídele a Claude que ejecute este comando:
Una línea que establece CLAUDE_CODE_USE_BEDROCK o CLAUDE_CODE_USE_VERTEX en 1 significa que la variable llegó a la sesión. Si aparecen ambas, Claude Code usa Amazon Bedrock. Si no hay salida, ninguna de las dos llegó.El comando muestra la configuración, no el tráfico. Para confirmar las solicitudes en sí, búscalas en las métricas o los registros de solicitudes de tu propia cuenta de nube. Si, en cambio, el primer mensaje falla, consulta la solución de problemas de Amazon Bedrock o de Agent Platform.

Qué cambia respecto a las sesiones en la API de Anthropic

Una sesión que envía solicitudes de modelo a Amazon Bedrock o a Agent Platform de Google Cloud difiere de una sesión en la API de Anthropic en estos aspectos:
  • Políticas de claude.ai: la configuración administrada por el servidor no llega a estas sesiones. Tampoco llegan las políticas de organización que un Owner establece en la configuración de administración de Claude Code, por lo que Claude Code no las aplica dentro de la sesión. Coloca las reglas de las que dependes en el archivo de configuración administrada de la imagen del runner.
  • Skills de la cuenta: estas sesiones no descargan los skills habilitados para la cuenta de claude.ai de una persona. Consulta Cómo se ensambla la configuración de cada sesión.
  • Archivos: los archivos que las personas adjuntan a una sesión en claude.ai o en la aplicación móvil o de escritorio no llegan a ella, y Claude no puede devolver archivos con la herramienta SendUserFile. En su lugar, coloca los archivos de entrada en el repositorio o en el runner.
  • Selección de modelo: el plano de control de Anthropic envía el modelo de cada sesión y, cuando una sesión se inicia sin uno, Claude Code usa su modelo predeterminado para el proveedor. No puedes elegir el modelo con ANTHROPIC_MODEL ni con ANTHROPIC_DEFAULT_MODEL en el entorno del runner, pero sí puedes fijar a qué se resuelve un alias:
    • ANTHROPIC_MODEL y ANTHROPIC_DEFAULT_MODEL: el runner las elimina del entorno que pasa a las sesiones, aunque los ejemplos de las páginas de los proveedores establezcan ANTHROPIC_MODEL.
    • Variables de fijación por familia: las variables de Fijar versiones de modelos para Amazon Bedrock y Agent Platform sí llegan a las sesiones. Deciden a qué se resuelve un alias como opus, no a qué se resuelve un ID de modelo completo.
  • Modelos que tu cuenta no ofrece: una sesión puede fallar en un mensaje con un error que nombra el modelo. Habilita los modelos que tus desarrolladores pueden elegir, el modelo en segundo plano descrito en Fijar versiones de modelos y el modelo clasificador que usa el modo automático. En Amazon Bedrock, permite cada uno de ellos en tu política.
  • Búsqueda web y modo rápido: la búsqueda web no está disponible en Amazon Bedrock, y el modo rápido no está disponible en ninguno de los dos proveedores. Para otras capacidades que varían según el proveedor, consulta Capacidades de la CLI que varían según el proveedor.

Servidores MCP

Para que los servidores MCP estén disponibles en cada sesión, agrégalos al compilar la imagen con el mismo comando claude mcp add que se usa en una instalación de escritorio. Si tu runner es un proceso directo en lugar de un contenedor, ejecuta el mismo comando como el usuario del runner en el host y luego reinicia el runner: lee la configuración del host una sola vez al iniciar. El flag --scope user es obligatorio; el alcance local predeterminado escribe bajo una clave por directorio que el runner no transfiere a las sesiones. Por ejemplo, en tu Dockerfile:
El runner toma una instantánea de la configuración del host una sola vez al iniciar. La instantánea captura la clave mcpServers del .claude.json del host, que se encuentra junto a ~/.claude/ y no dentro de él, y el runner transfiere solo esa clave a la configuración aislada de cada sesión; el estado de la cuenta y el historial del proyecto se descartan. Para confirmar que los servidores llegaron a las sesiones, inicia una sesión en el entorno y pídele a Claude que enumere sus herramientas MCP; el runner también registra una advertencia al iniciar para cualquier entrada capturada cuyo type no reconoce y descarta esa entrada, para que puedas ver por qué ese servidor falta en las sesiones. Cuando SELF_HOSTED_RUNNER_HOST_CONFIG_DIR está definida, el runner lee .claude.json desde ese directorio, por lo que apuntar la variable a un directorio vacío también desactiva la transferencia de servidores MCP. Claude Code también carga servidores MCP desde otras fuentes:
  • El archivo MCP administrado de alcance empresarial en su ruta estándar del sistema: /etc/claude-code/managed-mcp.json en hosts de runner Linux, /Library/Application Support/ClaudeCode/managed-mcp.json en hosts macOS. Úsalo para flotas restringidas donde solo pueden cargarse los servidores que indica el administrador. Consulta control exclusivo con managed-mcp.json para conocer las reglas de precedencia. Cuando este archivo está en el host del runner, Claude Code omite los servidores MCP que el plano de control de Anthropic entrega a una sesión, incluidos los conectores de claude.ai, y los nombra en una advertencia en el stderr del proceso hijo de la sesión, que el runner registra en el nivel de registro debug. Antes de la v2.1.229, esas sesiones terminaban al iniciar con You cannot dynamically configure MCP servers when an enterprise MCP config is present.
  • La clave managedMcpServers en la configuración administrada del host del runner: proporciona servidores HTTP y SSE sin tomar el control exclusivo, por lo que los servidores de las demás fuentes se siguen cargando. Requiere Claude Code v2.1.259 o posterior.
  • <repo>/.mcp.json: alcance de proyecto. Fuera de un hilo de proyecto, haz commit del archivo en el repositorio y sus servidores se aprueban automáticamente en las sesiones en la nube. En una sesión con varios repositorios, se carga como máximo el archivo de un repositorio.
  • Un archivo --mcp-config desde tu script contenedor: agrega el flag después de "$@", como en exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.json. Para que los servidores de ese archivo sigan ejecutándose cuando los servidores entregados a la sesión se actualicen a mitad de la sesión, haz que tu script contenedor pase el entorno completo al proceso hijo, como hace exec. Antes de la v2.1.299, los servidores stdio, HTTP y SSE de ese archivo se cerraban en la primera actualización de ese tipo que no los nombraba.
Cuando la entrega de conectores está habilitada para tu organización, el plano de control de Anthropic entrega los conectores que configuraste en claude.ai a las sesiones creadas de forma interactiva mediante configuración MCP proporcionada por el servidor, enrutada a través de api.anthropic.com. Las sesiones creadas de forma programática, como los envíos desde la CLI, no reciben la entrega de conectores; en su lugar, proporciónales servidores MCP mediante cualquiera de las otras fuentes que enumera esta sección. El token OAuth del proceso hijo no incluye un alcance para obtener conectores directamente, así que el proceso hijo no intenta obtenerlos por sí mismo; la entrega la gestiona el servidor.
En una sesión que recibió un conector, no se conecta un servidor con el mismo nombre procedente del .claude.json del host o del .mcp.json de un repositorio. Para usar ambos, dale a tu servidor un nombre diferente.
settings.json no incluye definiciones de servidores MCP, y no existe un campo mcpServers de nivel superior en el esquema de configuración. En la configuración administrada, proporciona los servidores con la clave managedMcpServers. Las sesiones heredan el entorno del runner, así que define ENABLE_TOOL_SEARCH allí para controlar la búsqueda de herramientas MCP en cada sesión que inicie un runner; la página de MCP describe los valores.

Esperar a los servidores MCP antes del primer turno

Una sesión autohospedada espera brevemente a los servidores MCP que aún se están conectando, en dos momentos distintos. Si un servidor no llega a tiempo a una de las esperas, sus herramientas faltan cuando comienza el primer turno, y quedan disponibles más tarde sin que tengas que hacer nada. Las dos esperas son:
  • Inicio de la sesión: antes de que se tome la lista de herramientas por primera vez, la sesión espera de forma predeterminada hasta 5 segundos a un servidor HTTP o SSE cuya entrada defina alwaysLoad: true, o a todos los servidores cuando defines MCP_CONNECTION_NONBLOCKING=0 en el entorno del runner. En los demás casos, los servidores HTTP y SSE se conectan en segundo plano. Mientras la sesión espera aquí, tarda más en inicializarse. MCP_CONNECT_TIMEOUT_MS cambia el valor predeterminado de 5 segundos.
  • Primer turno: después de que llega el mensaje, el primer turno espera hasta 2 segundos a los servidores stdio que aún se están conectando. Mientras la sesión espera aquí, la primera respuesta tarda más. Para cambiar cuánto dura esta espera, define CLAUDE_CODE_MCP_STARTUP_WAIT_MS en el entorno del runner. No cambia qué servidores abarca la espera. Requiere Claude Code v2.1.274 o posterior.
claude mcp add no tiene un flag alwaysLoad. Para definir la clave, agrega el servidor con claude mcp add-json, que la recibe en el JSON del servidor y la escribe en .claude.json. En tu Dockerfile:
Si las herramientas de un servidor tampoco aparecen en turnos posteriores, comprueba si el servidor llegó a la sesión, como se describe en Servidores MCP.

Desactivar las herramientas de sesión integradas

El plano de control de Anthropic adjunta su propio servidor MCP, llamado Claude Code Remote, a las sesiones en la nube. Claude usa las herramientas del servidor para crear tareas programadas en la nube, iniciar y dirigir otras sesiones en la nube, adjuntar más repositorios y seguir la actividad de los pull requests. Para desactivar el servidor completo, agrega una regla de denegación a nivel de servidor a tu configuración. El plano de control registra el servidor con uno de tres nombres, según cómo se creó la sesión. Claude Code compara el nombre de una regla de forma exacta, incluidas mayúsculas y minúsculas, así que escribe la regla una vez por cada nombre como se muestra:
Una regla que nombra el servidor completo también cubre las herramientas que el servidor incorpore más adelante. Para desactivar una herramienta y conservar las demás, agrega dos guiones bajos más y el nombre de la herramienta a cada regla, como en mcp__Claude_Code_Remote__add_repo. Para impedir que el servidor se conecte en absoluto en lugar de quitar sus herramientas, agrega los tres nombres sin el prefijo mcp__ como entradas serverName en deniedMcpServers. Coloca las reglas en la configuración administrada por el servidor para que lleguen a las sesiones sin cambios en el runner, o en ~/.claude/settings.json en el runner. En un runner que envía solicitudes de modelo a Bedrock o Agent Platform, usa ese archivo, porque la configuración administrada por el servidor no llega a esas sesiones. Permisos y aprobación de herramientas explica cómo la configuración del runner llega a las sesiones. Para confirmar que las reglas surtieron efecto, inicia una sesión en el entorno y pídele a Claude que enumere sus herramientas MCP. Claude Code quita una herramienta denegada del contexto de Claude, por lo que las herramientas denegadas no aparecen en su respuesta.

Solicitar a sesiones que envíen su trabajo

Las sesiones alojadas por Anthropic ejecutan un hook Stop, el hook de Claude Code que se ejecuta cuando Claude termina de responder, que solicita a Claude que confirme y envíe su trabajo. El ejecutor no instala uno. Sin él, una sesión que termina con cambios no confirmados deja ese trabajo solo en el disco del ejecutor, y el botón Create PR en claude.ai/code permanece inactivo hasta que la rama existe en el remoto. La implementación de referencia a continuación tiene dos partes. Fusione el bloque de configuración en ~/.claude/settings.json en el host del ejecutor, que el ejecutor siembra en cada sesión, y guarde el script como ~/.claude/hooks/stop-hook-nudge.sh en el host del ejecutor y hágalo ejecutable:
El hook solicita a Claude que confirme y envíe antes de que la sesión termine, y permanece silencioso cuando el directorio no es un repositorio git o no tiene remoto. Para una sesión con varios repositorios, consulta a qué se refiere $CLAUDE_PROJECT_DIR. El hook Stop solo le hace una solicitud a Claude. Para que el commit y el push funcionen, configura: El hook Stop hace la solicitud como máximo una vez por turno. Si el commit o el push fallan, el turno termina de todos modos. Busca el error de git en la sesión y corrige lo que indique. En un checkout grande, aumenta timeout en la configuración del hook Stop y reinicia los ejecutores. El git status del script omite core.fsmonitor, por lo que puede tardar más que los 10 segundos establecidos ahí. Un hook cuyo tiempo de espera se agota termina el turno sin ninguna solicitud.

Submódulos y el hook Stop

El hook Stop comprueba en qué commit está cada submódulo, no los archivos dentro de él:
  • Un commit nuevo en un submódulo: se le solicita a Claude hasta que el repositorio principal lo registre, incluso si .gitmodules establece ignore = all para ese submódulo
  • Archivos no confirmados en un submódulo: no se le solicita nada a Claude, por lo que ese trabajo permanece solo en el disco del ejecutor
  • Commits no enviados en un submódulo: el hook Stop no comprueba si se enviaron, por lo que el repositorio principal puede apuntar a un commit que nadie más puede obtener

Reemplazar una copia anterior del script del hook Stop

Busca GIT_OPTIONAL_LOCKS en tu copia de stop-hook-nudge.sh. Si no aparece, tienes una copia anterior. Reemplázala con el script actual dondequiera que la hayas instalado y luego reinicia los ejecutores. Conserva la configuración de tu hook Stop, salvo para aumentar timeout en un checkout grande.
  • Cuándo: si tus sesiones se ejecutan con el sandbox de Bash activado, reemplázala ahora. De lo contrario, reemplázala en tu próxima compilación de imagen o reinicio del ejecutor.
  • Qué cambia: los archivos no confirmados en un submódulo ya no generan una solicitud a Claude. Un commit nuevo en un submódulo que .gitmodules marca con ignore = all ahora sí la genera.
Los hooks se ejecutan fuera del sandbox de Bash, por lo que las llamadas a git del script actual omiten algunas funciones de git que pueden iniciar un programa indicado en la configuración de git del checkout. No las omiten todas. No confíes solo en el sandbox, y aísla las sesiones como se describe en Reforzar tu despliegue.

Permisos y aprobación de herramientas

Una sesión autohospedada no tiene terminal adjunta, por lo que un mensaje de permiso sin respuesta detiene el turno hasta que el usuario responde en la UI. El plano de control de Anthropic envía la lista de herramientas de cada sesión y las reglas de permiso con la carga útil de trabajo; la configuración predeterminada aprueba previamente llamadas de herramientas rutinarias, incluido Bash, y las sesiones en la nube aprueban previamente ediciones de archivos independientemente del modo. Una llamada que nada aprueba previamente solicita a través de la UI de sesión.
Solo fije el modo automático en un entorno cuyo contenedor de sesión se ejecute con egreso de red de negación predeterminada y el resto de la sección de endurecimiento en su lugar. Las llamadas de herramientas rutinarias, incluidas solicitudes de red Bash, se ejecutan sin un humano en el bucle tanto en el conjunto de herramientas aprobadas previamente predeterminado como en modo automático, por lo que el límite de red es lo que limita dónde esas llamadas pueden llegar.
Para mantener solicitudes al mínimo independientemente de lo que el plano de control envíe, fije el modo automático desde su script contenedor o hook command. El modo automático permite que las sesiones se ejecuten sin solicitudes de permiso rutinarias: un modelo clasificador separado revisa acciones antes de que se ejecuten y bloquea las que rechaza, y las reglas de solicitud explícita aún fuerzan una solicitud; la página de modos de permiso cubre lo que el clasificador verifica. El ejecutor agrega banderas calculadas por servidor antes de invocar el contenedor, y para banderas de valor único como --permission-mode el analizador honra la última ocurrencia, por lo que una bandera que agrega después de "$@" anula el valor enviado por servidor:
Para aprobar previamente herramientas específicas en su lugar, agregue --allowed-tools con sus reglas, por ejemplo --allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*". Las banderas de lista como --allowed-tools y --disallowed-tools se acumulan en ocurrencias en lugar de anular, por lo que sus reglas se aplican además de cualquier regla que el plano de control envíe. Para estrechar, agregue --disallowed-tools, que niega herramientas incluso si otra regla las permite.

Cómo se ensambla la configuración de cada sesión

El ejecutor da a cada sesión su propio directorio de configuración, sembrado desde una instantánea en memoria de ~/.claude/ del host que el ejecutor captura una vez al inicio: settings.json, CLAUDE.md, hooks, agentes, comandos y habilidades en su imagen de ejecutor se aplican a cada sesión como la línea de base a nivel de usuario. Si cambia la configuración en un host en ejecución, el cambio tiene efecto solo después de reiniciar el ejecutor. Establezca SELF_HOSTED_RUNNER_HOST_CONFIG_DIR para sembrar desde una ruta diferente, o apúntelo a un directorio vacío para deshabilitar la siembra. Las sesiones también leen estos archivos de configuración: Para conocer el orden en que se aplican estas fuentes, consulta precedencia de configuración. Cuando el plano de control de Anthropic proporciona a una sesión hooks de Claude Code, el ejecutor los instala junto a tu propia configuración, no encima de ella. Requiere Claude Code v2.1.229 o posterior; los ejecutores anteriores ignoran los hooks suministrados. Los hooks suministrados ejecutan python3, así que instala python3 en tu imagen del ejecutor.
  • Dónde aterrizan: el ejecutor escribe cada script de hook suministrado en un subdirectorio reservado hooks/.ccr-launcher/ del directorio de configuración de la sesión y registra los scripts en un archivo de configuración separado que pasa a la sesión con --settings, dejando el settings.json sembrado y sus propios scripts en hooks/<name> sin tocar. El ejecutor recrea el subdirectorio reservado para cada sesión y no siembra contenido del host en ~/.claude/hooks/.ccr-launcher/ en sesiones.
  • Quién los autor: el plano de control puebla los scripts desde constantes fijas en su propia implementación, nunca desde entrada por sesión o de terceros.
  • Qué aún los rige: los hooks entregados a través de --settings entran en la configuración de hook ordinaria fusionada, no en el nivel administrado, por lo que su configuración administrada aún se aplica. disableAllHooks los deshabilita, y no están entre las categorías que allowManagedHooksOnly mantiene cargadas.
Cuando una persona inicia su propia sesión, Claude Code descarga los skills habilitados para su cuenta de claude.ai en el directorio de configuración de esa sesión. Una ejecución de una tarea programada en la nube no recibe los skills de su propietario, y una sesión que envía solicitudes de modelo a Bedrock o Agent Platform no descarga ninguno. Para un skill que esas sesiones necesiten, agrégalo a tu imagen del ejecutor o, fuera de un hilo de un proyecto, haz commit de él en el .claude/skills/ del repositorio. Fuera de las sesiones de Claude Tag, una sesión en un entorno autohospedado se ejecuta con la memoria automática desactivada de forma predeterminada. Para instrucciones que deban mantenerse entre sesiones, usa el CLAUDE.md de tu imagen del ejecutor o del repositorio. La instantánea del ~/.claude/ del host que toma el ejecutor excluye el directorio projects/. La ubicación de almacenamiento predeterminada de la memoria automática está dentro de ese directorio. Si colocas archivos de memoria allí, el ejecutor no los copia a las sesiones, y no activan la memoria automática.

Detener las sesiones que no logran obtener la configuración administrada por el servidor

Para evitar que una sesión que obtiene la configuración administrada por el servidor se inicie cuando esa obtención falla, establece forceRemoteSettingsRefresh en true en el managed-settings.json de tu imagen del ejecutor. Una respuesta de la API de Anthropic que indica que tu organización no tiene configuración administrada por el servidor cuenta como una obtención correcta. Antes de agregar la clave, comprueba si el entorno ejecuta sesiones de canales de Claude Tag o envía solicitudes de modelo a Bedrock o Agent Platform:
  • Sesiones de canales de Claude Tag: omite la clave en los ejecutores de un entorno al que un Owner dirige canales de Claude Tag. La API de Anthropic rechaza la solicitud de configuración administrada por el servidor de estas sesiones, por lo que, con la clave establecida, no logran iniciarse. Coloca sus reglas en el managed-settings.json de la imagen del ejecutor, que siguen leyendo. Para usar la clave en tus demás sesiones, ejecútalas en un entorno independiente.
  • Bedrock o Agent Platform: las sesiones en estos proveedores no obtienen la configuración administrada por el servidor, por lo que se inician tanto si estableces la clave como si no. En su lugar, aplica tus reglas desde el mismo managed-settings.json.
Este managed-settings.json establece la clave:
managed-settings.json
Establece la clave en ese archivo aunque también la establezcas en la consola de administración de claude.ai. Una sesión nueva no tiene una copia en caché de tu configuración administrada por el servidor, así que, si estableces la clave solo en la consola, la sesión todavía no la tiene. Si su obtención falla entonces, la sesión se ejecuta sin esa configuración hasta que una obtención posterior tenga éxito. Para confirmar que una obtención fallida de la configuración es la razón por la que una sesión no se inició, revisa la última salida de Claude Code en la sesión o el registro del ejecutor. Incluye Your organization requires remote managed settings to load, but they could not be loaded.

Configuración de repositorios en sesiones con varios repositorios

En un hilo de un proyecto, ningún .claude/settings.json de repositorio tiene efecto como configuración del proyecto y no se carga ningún .mcp.json ni ningún skill de repositorio, dondequiera que se inicie el hilo. Para dar un skill a esos hilos, agrégalo a tu imagen del ejecutor. En cualquier otra sesión con varios repositorios, Claude Code lee la configuración del proyecto desde el directorio en el que se inicia la sesión, por lo que como máximo el .claude/settings.json de un solo repositorio tiene efecto como configuración del proyecto. Un hook definido en el archivo de otro repositorio no se ejecuta, una regla de denegación en él no se aplica y su env no se establece.
  • --capacity 1, el valor predeterminado, con el checkout integrado: la sesión se inicia en el primer repositorio de su lista de repositorios. El .claude/settings.json de ese repositorio tiene efecto como configuración del proyecto y su .mcp.json se carga, y los de los demás repositorios no.
  • Un --capacity mayor que uno, o un hook checkout: la sesión se inicia en un directorio por sesión que contiene los checkouts. Ningún .claude/settings.json de repositorio tiene efecto como configuración del proyecto, ningún .mcp.json de repositorio se carga, y $CLAUDE_PROJECT_DIR en un comando de hook es ese directorio, no un checkout.
El CLAUDE.md de cada repositorio se carga dondequiera que se inicie la sesión. Fuera de un hilo de un proyecto, sus skills también se cargan. El ejecutor pasa cada repositorio a Claude Code como un directorio adicional, por lo que Claude Code también lee las claves enabledPlugins y extraKnownMarketplaces del .claude/settings.json de cada repositorio. Para ejecutar un hook o aplicar una regla de permisos en todas las sesiones, colócalo en ~/.claude/settings.json en el host del ejecutor. El ejecutor siembra el archivo del host en todas las sesiones, dondequiera que se inicie la sesión. Escribe la ruta en una regla Read o Edit como un patrón absoluto con // o relativo al directorio personal con ~/, porque los demás patrones se anclan a la fuente de configuración o al directorio actual.

Reglas de permiso confirmadas en el repositorio

No coloque una entrada "Edit", "Write" o "NotebookEdit" desnuda en un permissions.allow confirmado en el repositorio. Una regla de herramienta de archivo desnuda coincide con la herramienta independientemente de la ruta, otorgando escrituras en cualquier lugar del host en lugar de solo el espacio de trabajo, por lo que la guardia de confinamiento de alcance de escritura del ejecutor marca la sesión; con --confine-repo-settings enforce se niega a generar la sesión en lugar de registrar y continuar. Consulte la sección de endurecimiento. Un repositorio no necesita ninguna regla de herramienta de archivo en absoluto: las sesiones en la nube aprueban previamente ediciones de archivos independientemente del modo. Si confirma una regla, limítela al espacio de trabajo, como "Edit(/**)"; una barra diagonal inicial única es relativa a la raíz del proyecto, que es el espacio de trabajo de la sesión. Las reglas de herramienta de archivo desnudas están bien en el settings.json a nivel de host del operador, ya que ese archivo no está confirmado en el repositorio. Un defaultMode de auto solo se honra desde el archivo de configuración a nivel de imagen o a nivel de usuario, por lo que un repositorio verificado no puede otorgarse a sí mismo modo automático. Para qué modos aceptan las sesiones en la nube y la sintaxis de regla completa, consulte modos de permiso.

Qué sigue