El modo headless de Claude Code es el flag -p. claude -p "tu prompt" ejecuta Claude Code una vez sin la interfaz interactiva: lee tu prompt (y todo lo que le pases por pipe), trabaja con las herramientas que permites, imprime el resultado y sale. El código de salida 0 significa éxito; cualquier otro, que la ejecución falló. Eso lo convierte en una herramienta de línea de comandos normal que puedes meter en scripts de shell, tareas de cron, git hooks y pipelines de CI.
Esta guía cubre los flags que de verdad vas a usar: formatos de salida, permisos de herramientas, límites de turnos y de presupuesto, --bare, continuar sesiones y pasar datos por pipe, seguidos de patrones listos para usar en scripts, cron y CI, y de cuándo pasar al Agent SDK. Cada flag está comprobado con la referencia oficial de la CLI a octubre de 2026.
Lo básico
claude -p "What does the auth module do?"
-p y --print son el mismo flag. La mayoría de las opciones interactivas se combinan con él, como --model, --effort, --append-system-prompt y --mcp-config. Algunas opciones solo existen en modo print, entre ellas --max-turns, --max-budget-usd, --json-schema y --no-session-persistence.
Dos detalles importan en los scripts:
- Si pasas un flag no válido, el error va a stderr antes de que empiece la ejecución.
- Si algo falla dentro de la ejecución, como que falte la autenticación, el fallo se imprime como resultado en stdout. Comprueba el código de salida, no solo la salida.
Datos por pipe de entrada, resultados redirigidos a la salida
El modo headless lee stdin, así que los logs, los diffs y los archivos entran como en cualquier herramienta Unix:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
git diff main | claude -p "Review this diff for bugs. List each as file:line and one sentence."
El stdin por pipe está limitado a 10MB. Por encima de eso, Claude Code sale con un error, así que para entradas grandes escribe el contenido en un archivo y nombra la ruta en el prompt.
También puedes conectarlo en package.json, como hace la documentación oficial con un linter de erratas:
{
"scripts": {
"lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\""
}
}
Elige un formato de salida
--output-format acepta tres valores:
| Formato | Qué obtienes | Para qué usarlo |
|---|---|---|
text (predeterminado) | La respuesta final como texto plano | Personas, archivos, scripts sencillos |
json | Un único objeto JSON al final | Scripts que necesitan la respuesta y los metadatos |
stream-json | Un evento JSON por línea a medida que ocurre | Progreso en vivo, interfaces propias, logs |
El objeto JSON lleva la respuesta en result, más session_id, cifras de uso y total_cost_usd. Ese coste es una estimación del lado del cliente y puede diferir de tu factura.
claude -p "Summarize this project" --output-format json | jq -r '.result'
Salida estructurada con —json-schema
Cuando un script necesita datos en lugar de prosa, pasa un JSON Schema. El resultado validado llega en structured_output:
claude -p "Extract the main function names from auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'
Si la ejecución termina sin una salida estructurada válida, por ejemplo porque Claude se negó, el campo no aparece. Compruébalo antes de usarlo:
out=$(claude -p "Extract the main function names from auth.py" --output-format json --json-schema "$(cat schema.json)")
echo "$out" | jq -e '.structured_output' > /dev/null || { echo "no structured output" >&2; exit 1; }
Eventos en streaming
stream-json requiere --verbose. Añade --include-partial-messages para recibir los tokens a medida que se generan. Esto imprime solo el texto en streaming:
claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
El primer evento suele ser system/init, con el modelo, las herramientas, los servidores MCP y los plugins. La última línea es un mensaje result con el texto final, el coste y los metadatos de la sesión. Los reintentos aparecen como eventos system/api_retry. Un control útil para CI: cuando pasas --mcp-config, una entrada de servidor que no supera la validación se omite sin que falle la ejecución, y aparece en mcp_server_errors en el evento init. Haz fallar el job si ese array no está vacío.
Controla lo que puede hacer la ejecución
En un script no hay nadie para responder a una solicitud de permiso, así que decide de antemano qué puede hacer Claude.
--allowedTools preaprueba herramientas con la sintaxis de las reglas de permisos:
claude -p "Look at my staged changes and create an appropriate commit" \
--allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
El espacio antes de * importa: Bash(git diff *) coincide con los comandos que empiezan por git diff, mientras que Bash(git diff*) también coincidiría con git diff-index.
--permission-mode fija la base para todo lo que no está en la lista. A octubre de 2026 (Claude Code v2.1.302 y posteriores), una ejecución con -p empieza en modo Manual cuando nada fija un modo, y una llamada que necesita aprobación se deniega. Valores útiles:
| Modo | Comportamiento en una ejecución headless |
|---|---|
dontAsk | Se rechaza todo lo que no esté preaprobado. La opción predecible para scripts |
acceptEdits | Las ediciones de archivos se permiten sin preguntar |
plan | Solo leer y planificar, sin ediciones |
auto | Un clasificador revisa cada acción; un bloqueo deniega esa acción y la ejecución continúa |
bypassPermissions | Ninguna comprobación. Solo dentro de un contenedor o una VM desechables |
--tools restringe qué herramientas integradas existen siquiera, y --disallowedTools añade reglas deny. --permission-prompts none le dice a Claude que nadie puede aprobar solicitudes para que no siga reintentándolas; las denegaciones se listan en permission_denials en el resultado final. Por qué saltarse los permisos en tu propia máquina es arriesgado se explica en Saltarse los permisos en Claude Code.
Pon límites a cada ejecución sin supervisión
--max-turns 20se detiene tras ese número de turnos agénticos y sale con un error. Sin límite por defecto.--max-budget-usd 5.00se detiene cuando el gasto estimado alcanza esa cantidad. Usa la estimación del lado del cliente y el gasto puede superar ligeramente el tope, así que deja margen.timeoutde tu shell o de tu paso de CI sigue valiendo la pena. Si detienes una ejecución con SIGTERM, Claude Code sale con el código 143, no registra resultado para el turno sin terminar y aun así ejecuta los hooksSessionEnd.
Usa —bare para ejecuciones reproducibles
Sin --bare, claude -p carga lo mismo que una sesión interactiva: hooks, skills, plugins, servidores MCP, la memoria automática y CLAUDE.md, desde el directorio de trabajo y desde ~/.claude. Eso significa que el mismo comando se comporta de forma distinta en tu portátil y en un runner de CI.
claude --bare -p "Summarize README.md" --allowedTools "Read"
--bare se salta todo ese descubrimiento y arranca más rápido. Tú pasas solo lo que la ejecución necesita:
| Para cargar | Flag |
|---|---|
| Instrucciones extra | --append-system-prompt, --append-system-prompt-file |
| Ajustes | --settings <file-or-json> |
| Servidores MCP | --mcp-config <file-or-json> |
| Agentes personalizados | --agents <file-or-json> |
| Un plugin | --plugin-dir <path> |
El modo bare nunca lee credenciales OAuth ni el llavero del sistema. Con la API de Anthropic, define ANTHROPIC_API_KEY o proporciona un apiKeyHelper en el JSON de --settings. La documentación oficial recomienda --bare para llamadas desde scripts y dice que será el valor predeterminado de -p en una versión futura, así que escribirlo explícitamente ahora mantiene estables tus scripts cuando eso ocurra.
Un motivo más para tenerlo en cuenta: sin --bare, una ejecución con -p en un repo recién clonado ejecuta los hooks de proyecto y los servidores de .mcp.json de ese repo sin diálogo de confianza. No apuntes una ejecución headless sin bare a código que no hayas revisado.
Continuar y retomar sesiones
Las ejecuciones headless se guardan igual que las interactivas, así que puedes encadenar pasos:
# First request
claude -p "Review this codebase for performance issues"
# Continue the most recent conversation
claude -p "Now focus on the database queries" --continue
claude -p "Generate a summary of all issues found" --continue
Cuando hay varias ejecuciones a la vez, --continue puede elegir la equivocada. Captura en su lugar el ID de la sesión:
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
--resume encuentra la sesión por su ID desde cualquier directorio de la máquina, y también acepta un nombre o la ruta al transcript .jsonl de una sesión. --fork-session abre una rama nueva a partir de la conversación retomada en lugar de añadir a ella, y --no-session-persistence evita que las ejecuciones puntuales se guarden.
Patrones para scripts, cron y CI
Un bucle sobre archivos
La página oficial de buenas prácticas tiene una buena plantilla para un cambio mecánico en muchos archivos:
for file in $(cat files.txt); do
claude --bare -p "Migrate $file to the new logger API and commit. Reply OK or FAIL." \
--allowedTools "Read,Edit,Bash(git add *),Bash(git commit *)" \
--permission-mode dontAsk \
--max-turns 20
done
Ejecútalo primero con tres archivos, lee los commits, ajusta el prompt y luego ejecuta el resto. Una ejecución que alcanza el límite de turnos sale con un error, que tu bucle puede registrar.
Un script de revisión
gh pr diff "$1" | claude -p \
--append-system-prompt "You are a security engineer. Review for vulnerabilities." \
--output-format json
Guárdalo como review.sh y ejecuta bash review.sh 123; el texto de la revisión está en result.
Una tarea de cron nocturna
# crontab -e
0 2 * * * cd /srv/app && claude --bare -p "Run the test suite, fix failing tests, commit on the current branch. Do not push." --allowedTools "Read,Edit,Bash(npm test *),Bash(git add *),Bash(git commit *)" --permission-mode dontAsk --max-turns 30 --output-format json >> /var/log/claude-nightly.jsonl 2>> /var/log/claude-nightly.err
Cron se ejecuta con un entorno mínimo, así que asegúrate de que claude está en su PATH y de que ANTHROPIC_API_KEY está definida para ese usuario. Ejecútalo en una rama o en un worktree, no en main. Si añades --worktree, ten en cuenta que las ejecuciones -p --worktree no se limpian solas; elimínalas con git worktree remove <path>. Para la configuración nocturna completa con presupuestos y revisión por la mañana, consulta Cómo ejecutar Claude Code durante la noche, y para los temporizadores de cada sistema operativo, Cómo programar tareas de Claude Code.
Pipelines de CI
En CI, usa --bare, guarda la API key en el almacén de secretos de CI y trata el código de salida como el resultado del paso. Para GitHub en concreto, la action oficial lo envuelve por ti, con comentarios en los PR y menciones a @claude: consulta Claude Code GitHub Actions. Si usas una suscripción en lugar de una API key, claude setup-token imprime un token OAuth de larga duración para CI y scripts; no se guarda, así que almacénalo como secreto enseguida. Recuerda que --bare ignora OAuth, así que un token de suscripción necesita una ejecución sin bare.
Errores habituales
- Skills y comandos de barra. Las skills de usuario funcionan si pones
/skill-nameen el prompt. Los comandos que solo existen en la interfaz de terminal, como/login, no. - Denegaciones silenciosas de herramientas. Si Claude dice que no pudo hacer algo, revisa
permission_denialsen el resultado JSON y añade la regla que falta a--allowedTools. - Trabajo en segundo plano. Una ejecución con
-ppuede quedarse abierta esperando a comandos en segundo plano o subagentes que Claude lanzó; por defecto deja de esperar tras 10 minutos de inactividad. - Reemplazar el system prompt.
--system-promptreemplaza por completo el prompt predeterminado, incluida la instantánea del estado de git. Prefiere--append-system-promptsalvo que sepas que quieres empezar desde cero.
Cuándo pasar al Agent SDK
claude -p es el motor del Agent SDK detrás de una CLI. En cuanto tu script empiece a analizar streams, gestionar aprobaciones o manejar muchas sesiones, pasa al Agent SDK para Python o TypeScript. Te da objetos de mensaje nativos, callbacks para aprobar herramientas y salidas estructuradas en código, sin tener que lanzar procesos de shell. El artículo Buenas prácticas de Claude Code cubre qué dejar preparado antes de que cualquier ejecución funcione sin supervisión.
Cómo hacerlo con AgentVera
Las ejecuciones headless son de lanzar y olvidar; a veces quieres la misma automatización con una terminal que puedas ver. En AgentVera, las tareas programadas (en todos los planes) envían un prompt a un agente según un horario cron mientras la app está abierta, y el agente se ejecuta en un panel real que puedes abrir y dirigir. Las automatizaciones (Pro y Team) arrancan un agente cuando la CI se pone en rojo o un PR recibe un comentario, y nunca hacen push a la rama predeterminada. El centro de CI/CD lista las ejecuciones de GitHub Actions, GitLab y Jenkins y puede pasar un log fallido a un agente. Night shift (Pro y Team) ejecuta el trabajo en cola durante la noche en un worktree, en una rama night/<date>-<slug>, hace commit sin push y deja un informe para la mañana. Descarga AgentVera si quieres que esas ejecuciones se vean en lugar de quedar enterradas en un archivo de log.
Para terminar
La receta básica del Claude Code headless es corta: claude --bare -p "clear task with a check", una lista explícita de --allowedTools, --permission-mode dontAsk, --max-turns, --output-format json y un código de salida que tu script respete. Empieza poco a poco, lee lo que producen las primeras ejecuciones y solo entonces ponlo en un temporizador.