Probar entornos autohospedados de extremo a extremo
Verifique una imagen de ejecutor autohospedado desde CI: envíe una sesión con la CLI, lea las respuestas de Claude a través de un hook Stop y ejecute el bucle completo.
Los entornos autohospedados están en versión beta pública en planes Team y Enterprise; Disponibilidad y limitaciones cubre la ruta de habilitación. Esta página es la receta de prueba de CI; consulte el inicio rápido para la configuración y Implementar en producción para las recetas de flota.
En un entorno autohospedado, las sesiones en la nube de Claude Code se ejecutan en una imagen de ejecutor que usted crea y mantiene. Antes de implementar una nueva imagen en su entorno de producción, ejecute una sesión completa contra un entorno de prueba desde un script: cree una sesión, lea la respuesta de Claude, envíe un seguimiento y lea esa respuesta también. Esta es la forma de una prueba de humo de CI que verifica su imagen de ejecutor, acceso a git y cualquier herramienta personalizada antes de que promueva un cambio.
Esta receta asume que ya ha configurado un entorno y un ejecutor, y que su trabajo de CI inicia el proceso del ejecutor en el mismo host que el script de prueba, la configuración natural para probar una nueva imagen de ejecutor. Un hook Stop que instala en el ejecutor escribe la respuesta final de cada turno en un archivo local, y el script la lee desde allí, por lo que las únicas llamadas a la API de Anthropic son los dos envíos en sí. Si sus ejecutores de prueba están en infraestructura separada, consulte Ejecutores de prueba remotos.
Instale el hook de captura en su ejecutor de prueba
La lectura funciona a través de un hook Stop de Claude Code: cuando Claude termina un turno, el hook recibe el mensaje final del asistente como last_assistant_message en su JSON de stdin y lo añade a $E2E_REPLY_DIR/<session_id>.txt. Instálelo de la misma manera que el hook Stop commit-nudge, en ~/.claude/ del host del ejecutor, que el ejecutor siembra en cada sesión.
Guarde los archivos del hook
Guarde los dos archivos a continuación en el host del ejecutor:
- El bloque de configuración: fusione en
~/.claude/settings.jsonen el host del ejecutor - El script: guarde como
~/.claude/hooks/e2e-stop-hook-capture.shen el host del ejecutor y hágalo ejecutable
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"timeout": 10,
"command": "\"$CLAUDE_CONFIG_DIR/hooks/e2e-stop-hook-capture.sh\""
}
]
}
]
}
}
#!/bin/sh
# Stop hook for testing a self-hosted environment end to end: writes each
# turn's final assistant reply to $E2E_REPLY_DIR/<session_id>.txt so a
# co-located test driver can read it without calling the Anthropic API.
# Install on the TEST runner only. Requires jq.
# No-op unless the driver is listening. Never fail the turn.
[ -n "${E2E_REPLY_DIR:-}" ] && [ -d "$E2E_REPLY_DIR" ] || exit 0
# CLAUDE_CODE_REMOTE_SESSION_ID is exported in cse_... form; the session
# id the dispatch CLI prints is in session_... form. Same id, different
# prefix.
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0
# last_assistant_message is absent when the final assistant turn had no
# text, such as a tool-use-only turn. The `// empty` filter makes that a
# zero-byte write rather than the literal string "null".
jq -r '.last_assistant_message // empty' >> "$E2E_REPLY_DIR/$sid.txt" 2>/dev/null
exit 0
Antes de iniciar el ejecutor
El hook tiene estos requisitos:
- Instálelo antes de iniciar el ejecutor. El ejecutor toma una instantánea de
~/.claude/una vez al inicio, por lo que un hook añadido a un ejecutor en ejecución solo tiene efecto después de un reinicio. - Exporte
E2E_REPLY_DIRal proceso del ejecutor. El hook es una operación sin efecto cuando la variable no está configurada o el directorio no existe, así que configúrelo donde inicie el ejecutor, como la unidad systemd, especificación de pod o paso de CI. El script de prueba a continuación también lo requiere.
Instale este hook solo en ejecutores que sirvan su entorno de prueba. Escribe la respuesta final de cada sesión en disco siempre que E2E_REPLY_DIR exista, lo cual es inofensivo en un ejecutor de CI desechable pero no algo que deba llevar a una imagen de ejecutor de entorno de producción donde la variable podría configurarse accidentalmente.
Ejecute el bucle de prueba
Los indicadores de envío --environment y --ref requieren Claude Code v2.1.224 o posterior en la máquina que ejecuta el script, el mismo piso que el ejecutor en sí. Con el hook en su lugar y un ejecutor iniciado en este host, el script de prueba:
- Crea una sesión en el entorno de prueba con
claude -p "<prompt>" --environment <environment-id> --output-format json, ejecutado desde un checkout de git para que la CLI pueda detectar automáticamente el repositorio desde el remotoorigin. El--ref <branch>opcional basa el checkout de la sesión en una ref nombrada en lugar de HEAD local. El comando crea la sesión, imprime una línea de JSON que contienesession_idy sale sin esperar la respuesta de Claude. - Espera a que la respuesta aparezca en
$E2E_REPLY_DIR/<session_id>.txt, escrita por el hook Stop en el ejecutor una vez que el turno se completa. - Envía un seguimiento con
claude -p "<message>" --cloud <session_id> --output-format json(consulte Enviar un mensaje de seguimiento a una sesión en ejecución), que publica un evento de usuario en la sesión existente y sale. - Espera la respuesta del seguimiento de la misma manera que el paso 2.
Comportamiento de envío de `--environment`
Claude Code crea la sesión, imprime el ID de sesión y un enlace a ella, y sale.
El indicador tiene precedencia sobre la configuración remote.defaultEnvironmentId. No admite --output-format stream-json y no se puede combinar con indicadores que reanuden, se adjunten o preconfiguran una sesión, como --resume, --continue, --teleport, --session-id o --init-only. --cloud se rechaza con un ID de sesión o URL, y en ejecuciones no interactivas cuando lleva una descripción. Un --cloud desnudo se trata como ausente. Desde una terminal, puede pasar la tarea como la descripción de --cloud en lugar de un prompt posicional.
Script de ejemplo
El script a continuación ejecuta el bucle completo contra $CLAUDE_TEST_ENVIRONMENT_ID, el ID ccpool_... de su entorno de prueba, que se muestra en el diálogo de detalles del entorno en la página de administración o se devuelve por la llamada create-environment, y afirma una frase centinela en cada respuesta. Ejecute desde un checkout de git del repositorio en el que desea que la sesión funcione, después de iniciar un ejecutor en este host con el hook de captura instalado y E2E_REPLY_DIR exportado.
#!/usr/bin/env bash
# End-to-end test against a self-hosted environment, using Stop-hook read-back.
# Prereqs: `claude auth login` has been run on this machine (see "Authenticate
# from CI" below); jq is installed; CLAUDE_TEST_ENVIRONMENT_ID names an
# environment whose runner is the one on this host, with the capture hook
# installed and E2E_REPLY_DIR in its environment.
set -euo pipefail
: "${CLAUDE_TEST_ENVIRONMENT_ID:=${CLAUDE_TEST_POOL_ID:-}}" # CLAUDE_TEST_POOL_ID is the legacy spelling
: "${CLAUDE_TEST_ENVIRONMENT_ID:?set CLAUDE_TEST_ENVIRONMENT_ID to a ccpool_... id served by a runner on this host}"
: "${E2E_REPLY_DIR:?set E2E_REPLY_DIR to the directory the Stop hook on your test runner writes to, and export it to the runner process}"
: "${TEST_REPO_REF:=main}"
[ -d "$E2E_REPLY_DIR" ] || {
echo "FAIL: E2E_REPLY_DIR ($E2E_REPLY_DIR) does not exist. The Stop hook on the runner needs it." >&2
exit 1
}
# Waits until $E2E_REPLY_DIR/<session_id>.txt contains $2, or fails after
# 90 seconds. Tune the timeout to your environment's cold-start time. The
# file is written by the Stop hook on the runner.
await_reply() {
local expect="$2" f="$E2E_REPLY_DIR/$1.txt"
local deadline=$(($(date +%s) + 90))
while :; do
if [ -f "$f" ] && grep -qF -- "$expect" "$f"; then
return
fi
[ "$(date +%s)" -lt "$deadline" ] || {
echo "FAIL: '$expect' not in $f within 90s. The Stop hook on the runner did not write it." >&2
echo "-- $E2E_REPLY_DIR contents --" >&2; ls -la "$E2E_REPLY_DIR" >&2
[ -f "$f" ] && { echo "-- $f --" >&2; cat "$f" >&2; }
exit 1
}
sleep 1
done
}
# 1. Create the session on the test environment. Run from a git checkout
# so the CLI can auto-detect the repo. --ref pins the checkout to a named
# ref regardless of local HEAD.
TURN1="e2e-probe-$(date +%s)-$$: say exactly 'ok: custom tools are reachable' and nothing else"
EXPECT1="ok: custom tools are reachable"
create_json=$(claude -p "$TURN1" --environment "$CLAUDE_TEST_ENVIRONMENT_ID" \
--ref "$TEST_REPO_REF" --output-format json)
echo "create: $create_json"
SESSION_ID=$(jq -er '.session_id' <<<"$create_json")
# 2. Wait for the turn-1 reply.
await_reply "$SESSION_ID" "$EXPECT1"
echo "turn-1 reply ok"
# 3. Post a follow-up via the CLI.
TURN2="e2e-probe-followup-$(date +%s): say exactly 'ok: follow-up delivered' and nothing else"
EXPECT2="ok: follow-up delivered"
followup_json=$(claude -p "$TURN2" --cloud "$SESSION_ID" --output-format json)
echo "followup: $followup_json"
jq -e '.ok == true' <<<"$followup_json" >/dev/null
# 4. Wait for the turn-2 reply.
await_reply "$SESSION_ID" "$EXPECT2"
echo "turn-2 reply ok"
echo "PASS: test-environment round-trip (session $SESSION_ID)"
Reemplace los prompts TURN1/TURN2 y los centinelas EXPECT1/EXPECT2 con lo que sea que ejercite su configuración, como pedirle a Claude que ejecute una de sus herramientas MCP personalizadas y afirmar su salida.
Ejecutores de prueba remotos
Si sus ejecutores de prueba están en infraestructura separada, como una flota de Kubernetes persistente con la que su trabajo de CI no puede compartir un sistema de archivos, cambie la escritura de archivo en el hook Stop por un POST a un endpoint que su controlador escuche:
#!/bin/sh
# Variant of the capture hook for runners on separate infrastructure.
# Set E2E_REPLY_URL on the runner to an endpoint the driver controls.
[ -n "${E2E_REPLY_URL:-}" ] || exit 0
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0
jq -r '.last_assistant_message // empty' | \
curl -fsS -X POST --data-binary @- "$E2E_REPLY_URL/$sid" >/dev/null 2>&1
exit 0
En el lado del controlador, ejecute cualquier cosa que acepte el POST y mantenga la respuesta hasta que la prueba la solicite, como un pequeño oyente HTTP dentro del trabajo de CI o un receptor de webhook que ya ejecuta. El hook se ejecuta en su infraestructura, por lo que el endpoint solo necesita ser accesible desde sus ejecutores.
Autentíquese desde CI
Tanto claude -p ... --environment como claude -p ... --cloud se autentican con un token OAuth de claude.ai; las claves API, como sk-ant-xxxxx, no se aceptan para ninguna de las dos llamadas. Dos enfoques hacen que un token esté disponible en CI.
Host de CI de larga duración
Ejecute claude auth login una vez de forma interactiva en la máquina que ejecuta el script, usando una cuenta de usuario dedicada para automatización. Claude Code almacena el token en el llavero del SO en macOS, o en ~/.claude/.credentials.json en Linux y Windows. En un host macOS cuyo Keychain no se puede escribir, como es típico en una sesión SSH donde el Keychain de inicio de sesión permanece bloqueado, Claude Code almacena el token en ~/.claude/.credentials.json también. Consulte Gestión de credenciales.
La CLI actualiza automáticamente el token de acceso de corta duración en cada invocación, pero la concesión de token de actualización subyacente está limitada a 30 días desde el inicio de sesión inicial, así que vuelva a ejecutar claude auth login de forma interactiva en ese host cada 30 días.
Ejecutores de CI efímeros
No hay un token de CI de larga duración para esto hoy. El alcance que otorga control de sesión remota, user:sessions:claude_code, está limitado en el servidor a 30 días, por lo que claude setup-token, que acuña un token de solo inferencia de un año, no lo cubre. El secreto del entorno tampoco se acepta, ya que solo autoriza a un ejecutor a registrarse con el entorno, no a crear sesiones.
Para aprovisionar un inicio de sesión almacenado en un ejecutor efímero, configure CLAUDE_CODE_OAUTH_REFRESH_TOKEN y CLAUDE_CODE_OAUTH_SCOPES para que claude auth login intercambie el token sin un navegador; el mismo límite de 30 días se aplica a la concesión de actualización. Póngase en contacto con su equipo de cuenta de Anthropic si necesita una ruta de identidad de máquina que no esté vinculada a una cuenta humana.
Cree un entorno de prueba dedicado
Cree y elimine entornos mediante programación para que cada ejecución de CI obtenga uno limpio; el ejecutor que su trabajo de CI inicia se registra en el entorno nuevo. Las llamadas de creación y eliminación a continuación son los mismos endpoints que la página de administración Cloud environments en claude.ai usa, y requieren el encabezado anthropic-beta: ccr-byoc-2025-07-29.
Acuñe el token de administrador
$ADMIN_TOKEN es un token de acceso OAuth de claude.ai para una cuenta que tiene un rol de Propietario, acuñado de la misma manera que Autentíquese desde CI:
- Acuñelo: ejecute
claude auth logincon una cuenta que tenga un rol de Propietario, luego lea el token de acceso actual desde donde Host de CI de larga duración dice que Claude Code lo almacenó. - Léalo fresco en cada ejecución: la CLI rota el token de acceso, y el mismo límite de concesión de actualización de 30 días se aplica, así que no almacene una copia.
- Páselo a través de stdin: como hace el ejemplo, para que el token nunca llegue a la lista de argumentos de curl o su registro de compilación.
Cree el entorno
Capture la respuesta sin ecoarla: pool_secret es una credencial de larga duración que puede registrar ejecutores en el entorno, así que guárdela como un secreto de CI enmascarado e imprima solo el ID del entorno. La forma -H @- que mantiene el token fuera de la lista de procesos requiere curl 7.55 o posterior; curl más antiguo trata @- como un encabezado literal y envía la solicitud sin autorización.
create=$(curl -fsS -X POST -H @- \
-H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"name":"ci-test-environment"}' \
https://api.anthropic.com/v1/code/runners/self-hosted/pools \
<<<"Authorization: Bearer $ADMIN_TOKEN")
ENVIRONMENT_ID=$(jq -er .pool.pool_id <<<"$create")
ENVIRONMENT_SECRET=$(jq -er .pool_secret <<<"$create")
Hasta que un Propietario active Allow self-hosted environments para la organización, la llamada falla con un 403 permission_error que lee self-hosted runners are disabled by your organization's policy.
Inicie un ejecutor en este host con SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET=$ENVIRONMENT_SECRET, más el hook de captura y E2E_REPLY_DIR por Instale el hook de captura, luego ejecute el script de prueba.
Elimine el entorno
Elimine el entorno cuando la ejecución finalice, para que cada ejecución de CI comience limpia:
curl -fsS -X DELETE -H @- \
-H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
"https://api.anthropic.com/v1/code/runners/self-hosted/pools/$ENVIRONMENT_ID" \
<<<"Authorization: Bearer $ADMIN_TOKEN"