Claude Code en GitHub Actions: revisa PRs y explica fallos
En la práctica/ 11 min de lectura
Tres workflows de Claude Code en GitHub Actions, con permisos mínimos y límites de tiempo, y qué devuelve claude -p en CI.
- IA
- Claude Code
En este artículo (10)
Claude Code en GitHub Actions se instala con la action oficial anthropics/claude-code-action@v1 y una clave guardada como secreto del repositorio. Con eso puede revisar cada pull request, explicar por qué fallan los tests y responder cuando alguien escribe @claude, con permisos mínimos y un límite de tiempo por job.
Este tutorial monta esos tres casos con workflows listos para copiar, explica qué devuelve claude -p, el modo sin interfaz sobre el que se apoya todo esto, y cuánto costó cada prueba. Es la parte 9 de nuestra serie de Claude Code. Una aclaración desde el principio: los workflows siguen los ejemplos de la documentación oficial y validamos su YAML, pero no los corrimos en GitHub para esta guía; lo que sí corrimos, en local, es el mismo paso de explicación con claude -p.
Qué es claude -p y qué devuelve
Un runner de CI no tiene a nadie delante para escribir en la terminal. Para eso existe el modo sin interfaz: claude -p "prompt" recibe la tarea, trabaja sin preguntar y termina. Según la documentación del modo headless, puedes pasarle texto por la entrada estándar y elegir el formato de salida con --output-format: text, json o stream-json.
Para scripts, json es el útil. Un ejemplo mínimo:
echo "di solo: ok" | claude -p --max-turns 1 --output-format json
En nuestra prueba, con Claude Code 2.1.238, ese JSON traía entre otros estos campos:
| Campo | Qué contiene | Para qué sirve en CI |
|---|---|---|
result | La respuesta final en texto | Lo que publicas en el log o en un comentario |
is_error | true si la ejecución falló | Decidir si el paso falla |
num_turns | Cuántos turnos usó | Ver si se acerca a tu --max-turns |
total_cost_usd | El costo estimado de esa ejecución | Medir cuánto cuesta cada run |
session_id | El identificador de la sesión | Continuarla con --resume |
permission_denials | Las herramientas que se le negaron | Saber si le faltó un permiso |
--max-turns no aparece en la ayuda de claude --help de esa versión, pero la documentación de GitHub Actions lo recomienda en claude_args y la CLI lo aceptó en todas nuestras pruebas.
Instalar la action: el secreto y la app
La documentación de GitHub Actions da dos caminos, y los dos piden acceso de administrador al repositorio:
- Rápido: abre
claudeen el repositorio y ejecuta/install-github-app. Instala la app de Claude en GitHub, guarda el secreto y prepara un PR con el workflow. Necesita la CLI de GitHub (gh) con sesión iniciada y solo funciona con repositorios de github.com. - Manual: instala la app de Claude, añade el secreto en Settings → Secrets and variables → Actions y copia el workflow a
.github/workflows/.
El secreto es uno de dos: ANTHROPIC_API_KEY, una clave de la consola de Claude que se factura por uso, o CLAUDE_CODE_OAUTH_TOKEN, que generas en local con claude setup-token y usa tu suscripción. En el workflow se pasa a anthropic_api_key o a claude_code_oauth_token. Nunca va escrito en el archivo: siempre ${{ secrets.ANTHROPIC_API_KEY }}.
El flujo completo dentro de un job de CI queda así:
Desliza la figura para verla completa
Caso 1: revisar cada pull request
Este workflow es el ejemplo «Run a skill» de la documentación con dos añadidos de GitHub: un timeout-minutes y un bloque concurrency que cancela la revisión anterior cuando llega un push nuevo al mismo PR. Instala el plugin code-review y lo ejecuta en cada PR que se abre, se actualiza o pasa a listo para revisar:
# Revisión de cada pull request con el plugin code-review de Claude Code.
# Basado en el ejemplo «Run a skill» de code.claude.com/docs/en/github-actions.
# Claude deja comentarios en las líneas del PR, o uno solo si no encuentra nada.
name: Revisión de Claude
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Un push nuevo al mismo PR cancela la revisión anterior: no pagas dos.
concurrency:
group: claude-revision-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
plugins: "code-review@claude-code-plugins"
prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'
Dos líneas deciden dónde aparece la revisión. --comment hace que Claude comente en las líneas del PR, o deje un único comentario si no encuentra nada; sin esa opción, los hallazgos solo quedan en el log del run. Y claude_args nombra la herramienta de comentarios en línea, porque la action solo arranca ese servidor MCP si --allowedTools lo menciona. Según la documentación, Claude se salta los PR en borrador, los cerrados y los que ya tienen un comentario suyo, y en repositorios públicos no corre en PR que vienen de forks, porque GitHub no les pasa los secretos.
Si ya tienes un revisor propio, como el subagente de la parte 6, el checkout lo deja disponible en el runner: puedes cambiar el prompt para que lo use.
Caso 2: explicar por qué fallan los tests
Cuando un test falla en CI, lo primero es leer cientos de líneas de log. Este workflow guarda la salida de npm test y, solo si falla, le pide a Claude una explicación corta en el log del run:
# CI con explicación: si los tests fallan, Claude lee su salida y explica la
# causa en el log del run. No cambia ningún archivo.
name: CI
on:
pull_request:
push:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- name: Tests
id: tests
# shell: bash activa pipefail: si npm test falla, el paso falla aunque tee no.
shell: bash
run: npm test 2>&1 | tee test-output.txt
- name: Explicar el fallo
if: failure() && steps.tests.outcome == 'failure'
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Los tests de este repositorio fallaron. Su salida está en test-output.txt.
Explica en español, en menos de 15 líneas: qué test falla, la causa más
probable y qué archivo revisar. No cambies ningún archivo.
claude_args: '--max-turns 5 --allowedTools "Read,Grep,Glob"'
Tres detalles. shell: bash no es decorativo: con él, GitHub activa pipefail y el paso falla cuando falla npm test, aunque tee termine bien. La condición steps.tests.outcome == 'failure' hace que Claude no corra si lo que falló fue la instalación. Y --allowedTools "Read,Grep,Glob" le da lo justo para leer el archivo y el código, sin poder editar.
Este paso lo probamos en local con un script del ZIP que hace lo mismo con claude -p: corre los tests y, si fallan, le pasa la salida a Claude. Rompimos a propósito la tienda de la parte 8, subiendo el envío de 4,90 € a 5,90 €:
La explicación encontró los dos tests que fallan y la diferencia exacta de 100 céntimos, y mandó a revisar el archivo correcto. La causa que dio fue vaga («error sistemático en el cálculo del total»): no dijo que la constante del envío había cambiado. En la versión en bash del mismo script, con el mismo fallo, sí señaló que el envío debería ser 490. Eso es lo que puedes esperar de este paso: una pista buena para empezar, no un diagnóstico cerrado. Cada ejecución con Haiku costó entre 0,048 y 0,059 USD.
Caso 3: documentación con @claude
El tercer workflow no corre solo: espera a que alguien con acceso de escritura mencione @claude en un issue o un PR. Es el ejemplo de la documentación con un timeout-minutes y un tope de turnos:
# Claude responde cuando alguien con acceso de escritura escribe @claude en un
# issue o un PR: por ejemplo, «@claude documenta la función resumen en docs/».
# Basado en el ejemplo «Respond to @claude mentions» de la documentación.
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 20"
Para la documentación, el uso típico es un comentario en el PR de una funcionalidad: «@claude documenta en docs/checkout.md cómo funciona el envío gratis y añade un ejemplo». Claude responde en un comentario que va actualizando mientras trabaja y sube los cambios como commits. Por eso este workflow pide contents: write, y por eso lo que proponga se revisa antes de fusionar, como cualquier commit de otra persona.
Un detalle de la documentación que ahorra un mal rato: GitHub no dispara workflows con commits hechos con el GITHUB_TOKEN por defecto. Si pasas github_token: ${{ secrets.GITHUB_TOKEN }} a la action, tu CI no correrá sobre los commits de Claude; sin esa línea, la action se autentica como la app de Claude y el CI corre.
Permisos mínimos, límites de tiempo y costo
Cada workflow pide solo lo que usa:
Desliza la figura para verla completa
Los tres llevan id-token: write, que la action necesita para autenticarse como la app de Claude. Además, la action comprueba quién dispara cada run: en eventos de issues y PR, la persona necesita acceso de escritura al repositorio, y los bots quedan fuera salvo que los listes en allowed_bots, para que no se disparen en bucle.
Para que un run no se alargue ni se multiplique, hay tres frenos, todos en los workflows del ZIP:
--max-turnsenclaude_args: corta a Claude tras ese número de turnos.timeout-minutesen el job: GitHub mata el job si pasa de ese tiempo.concurrencyconcancel-in-progress: un push nuevo cancela el run anterior del mismo PR.
Cada run consume dos cosas: minutos de GitHub Actions y tokens de Claude, o tu suscripción si usas CLAUDE_CODE_OAUTH_TOKEN. Como referencia de nuestras pruebas en local, no de tarifas: explicar un fallo con Haiku costó unos 0,05 USD, y en la parte 6 revisar un archivo de 20 líneas con Sonnet costó unos 0,15 USD. El costo crece con el tamaño del diff, del CLAUDE.md y de los archivos que lea. Para saberlo en tu repositorio, mira total_cost_usd en tus primeras ejecuciones.
Revisar lo que propone
La regla es la misma que con una persona nueva en el equipo: nada entra a main sin revisión. En la práctica:
- Los comentarios de revisión son sugerencias: decide tú cuáles aplicar.
- Los commits de
@claudellegan a una rama o a un PR; pasan por tu CI y por tu revisión como los demás. - Protege
maincon reglas de rama para que nada, tampoco la app de Claude, pueda fusionar sin aprobación. - Escribe tus reglas de código en
CLAUDE.md: Claude lo lee en cada run, y la documentación recomienda mantenerlo corto, porque también cuesta tokens.
Errores comunes
- Escribir la clave en el YAML. Va siempre en un secreto. Si alguna vez la subiste, revócala en la consola: borrar el commit no basta.
- Dar
contents: writea todos los workflows. Solo el de@claudelo necesita. La revisión y la explicación funcionan con lectura. - Olvidar
shell: bashcontee. Sinpipefail, el paso de tests queda en verde aunque fallen, y Claude nunca corre. - No poner límites. Sin
--max-turnsnitimeout-minutes, un run que se enreda sigue gastando minutos y tokens. - Pasar
github_tokensin querer. Con el token por defecto, tu CI no corre sobre los commits de Claude. - Esperar revisiones en PR de forks. En repositorios públicos, GitHub no pasa secretos a esos runs.
Preguntas frecuentes
¿Uso una API key o mi suscripción?
Para un repositorio personal, el token de claude setup-token usa tu suscripción. Para una organización, la documentación recomienda una API key de la consola, porque el token queda atado a la suscripción de quien lo generó.
¿Claude puede hacer merge solo?
Con contents: write puede subir commits. Que pueda fusionar en main depende de tus reglas de protección de rama; configúralas para exigir aprobación humana.
¿Dónde veo lo que hizo en cada run?
En el log del job en la pestaña Actions. En modo interactivo, además, en el comentario que Claude va actualizando en el issue o el PR.
¿Funciona con GitLab?
Esta action es solo para GitHub. La documentación tiene una página aparte para GitLab CI/CD.
¿Y si quiero que planifique antes de cambiar nada?
--permission-mode plan en claude_args lo deja planificando sin editar, como en plan mode. Es útil para que proponga un plan en un issue antes de que alguien lo apruebe.
Checklist
- El secreto
ANTHROPIC_API_KEYoCLAUDE_CODE_OAUTH_TOKENestá en el repositorio, no en el YAML. - La app de Claude está instalada en el repositorio.
- Cada workflow tiene solo los permisos de su caso, más
id-token: write. - Los jobs tienen
timeout-minutes, y los pasos de Claude,--max-turns. - Probaste el paso de explicación en local con
explicar-falloantes de subirlo. mainexige revisión humana antes de fusionar.
Con estos tres workflows, Claude revisa, explica y documenta dentro de tu repositorio, y tú sigues decidiendo qué entra. Si lo que quieres es revisar en local antes de subir, empieza por el subagente revisor; para que los cambios lleguen con un plan detrás, plan mode; y para el formato en cada edición, el hook del linter. Si tu repositorio guarda secretos en local, revisa también el hook que protege tu .env. El método completo está en mi flujo de 4 pasos con Claude Code.
¿Viste esto en Instagram? Comenta BOT en el Reel o el carrusel y te mando la guía.
Escrito por

Publicado el 26 de octubre de 2026 · actualizado el 28 de octubre de 2026