Módulo 1: Claude Code en GitHub Actions
Tu Primer Workflow YAML con Claude Code
Tu Primer Workflow YAML con Claude Code
Descripción
Esta es la cápsula donde Claude Code deja de ser una herramienta local y empieza a ser un agente automático. Vas a crear, paso a paso, tu primer workflow de GitHub Actions que ejecuta Claude Code en cada pull request — sin que tengas que abrir nada, sin que dependa de tu memoria.
Al terminar la cápsula, vas a poder escribir un workflow YAML desde cero que se dispara en eventos de PR, ejecuta el SDK headless de Claude Code, y produce un output verificable. No es teoría — es un YAML ejecutable que copias, haces push, y ves correr en tu repositorio.
Anatomía de un Workflow de GitHub Actions
Antes de escribir código, una imagen mental: un workflow de GitHub Actions tiene tres niveles jerárquicos.
WORKFLOW (un archivo .yml en .github/workflows/)
├── name: nombre humano del workflow
├── on: cuándo se dispara (triggers)
└── jobs:
├── JOB-1
│ ├── runs-on: en qué máquina corre (ubuntu, macos, windows)
│ ├── steps:
│ │ ├── STEP-1 (ej. checkout del código)
│ │ ├── STEP-2 (ej. setup de Python)
│ │ ├── STEP-3 (ej. ejecutar Claude Code)
│ │ └── STEP-N
│ └── env / outputs / etc.
├── JOB-2 (puede correr en paralelo o esperar a JOB-1)
└── ...
Workflow es el archivo entero. Job es un conjunto de pasos que corren en una misma máquina. Step es la unidad atómica — una acción o un comando shell.
El Workflow Mínimo Funcional
Empezamos con un workflow que hace una sola cosa: cuando se abre un PR, ejecuta Claude Code para analizar el diff y publica el resultado en los logs.
Crea el archivo .github/workflows/claude-code-analysis.yml:
name: Claude Code Analysis
on:
pull_request:
types: [opened, synchronize]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Claude Code SDK
run: pip install anthropic
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr_diff.txt
echo "diff_size=$(wc -l < pr_diff.txt)" >> $GITHUB_OUTPUT
- name: Run Claude Code analysis
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python .github/scripts/analyze_pr.py
Y crea el script .github/scripts/analyze_pr.py:
"""Análisis básico de un PR con Claude Code SDK."""
import os
from pathlib import Path
from anthropic import Anthropic
client = Anthropic() # lee ANTHROPIC_API_KEY del environment
diff_text = Path("pr_diff.txt").read_text()
if not diff_text.strip():
print("No hay cambios para analizar.")
exit(0)
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=2000,
messages=[
{
"role": "user",
"content": (
"Analiza este diff de un pull request. "
"Identifica:\n"
"1. Qué cambia funcionalmente.\n"
"2. Posibles problemas (bugs, edge cases, seguridad).\n"
"3. Sugerencias concretas de mejora.\n\n"
f"Diff:\n```\n{diff_text}\n```"
),
}
],
)
print("=== Análisis de Claude Code ===")
print(response.content[0].text)
print(f"\nTokens usados: {response.usage.input_tokens} in / {response.usage.output_tokens} out")
Comando de ejecución: push estos dos archivos a una rama, abre un PR contra main, y espera unos segundos. El workflow se dispara automáticamente. En la pestaña "Actions" del repositorio vas a ver el run; en sus logs aparece el análisis de Claude Code.
Output esperado (ejemplo):
=== Análisis de Claude Code ===
Este PR modifica `src/payment_service.py` agregando una nueva función
`apply_discount(amount, percentage)`.
Posibles problemas:
1. La función no valida que `percentage` esté entre 0 y 100. Si pasas
`percentage=150`, devuelve un monto negativo.
2. La operación es punto flotante; con dinero, considera usar Decimal
para evitar errores de redondeo.
Sugerencias:
- Agregar validación de `percentage` con un raise apropiado.
- Cambiar a `Decimal` para los cálculos monetarios.
Tokens usados: 850 in / 245 out
Diseccionando el YAML pieza por pieza
Cada parte del workflow tiene un propósito específico. Vamos línea por línea.
Triggers: cuándo se dispara
on:
pull_request:
types: [opened, synchronize]
pull_requestactiva el workflow en eventos de PRtypes: [opened, synchronize]lo limita a dos eventos:opened— se abrió el PRsynchronize— se hizo push a la rama del PR (cambios nuevos)
Sin types, el workflow correría también en closed, reviewed, labeled, etc. — generando ejecuciones que no necesitas. Restringir es ahorrar costos.
Job y máquina
jobs:
analyze:
runs-on: ubuntu-latest
analyzees el nombre interno del job (usado para referencias)runs-on: ubuntu-latestes la máquina virtual donde corre. Para Claude Code en CI, Ubuntu es la opción más rápida y económica.
Steps esenciales
Step 1 — Checkout del código
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
actions/checkout@v4 es la action oficial que clona el repo en la máquina del runner. Crítico: fetch-depth: 0 clona el historial completo. Sin esto, el git diff del paso siguiente no puede comparar contra main porque el runner solo tiene un commit.
Step 2 — Setup de Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
Instala Python en el runner. Versión 3.11 es estable y soportada por el SDK de Anthropic.
Step 3 — Instalar el SDK
- name: Install Claude Code SDK
run: pip install anthropic
Instala el paquete oficial. Para producción, vas a querer pinear la versión:
pip install anthropic==0.39.0 (o la última estable).
Step 4 — Extraer el diff
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr_diff.txt
echo "diff_size=$(wc -l < pr_diff.txt)" >> $GITHUB_OUTPUT
github.base_refes la rama destino del PR (típicamentemain)origin/main...HEADes el rango del diff- El output se guarda en
pr_diff.txtpara que el step siguiente lo use echo ... >> $GITHUB_OUTPUTexporta la variablediff_sizepara uso opcional en steps posteriores
Step 5 — Ejecutar Claude Code
- name: Run Claude Code analysis
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python .github/scripts/analyze_pr.py
${{ secrets.ANTHROPIC_API_KEY }}referencia un secret configurado en el repo (la cápsula 03 lo desarrolla)envlo pasa como variable de environment al script Python- El script lee el diff y produce el análisis
Trabajando el Workflow Localmente Antes del Push
Una buena práctica: probar el script Python local antes de subirlo al CI.
# En tu máquina, en la raíz del repo
export ANTHROPIC_API_KEY="sk-ant-..."
# Genera un diff de prueba
git diff main...HEAD > pr_diff.txt
# Corre el script localmente
python .github/scripts/analyze_pr.py
Si el script funciona local, también va a funcionar en GitHub Actions (el environment es predecible). Esta práctica te ahorra el ciclo "push → ver fallar → corregir → push de nuevo".
Trampas Comunes en el Primer Workflow
Cinco errores que aparecen específicamente al escribir tu primer workflow de Claude Code.
Error 1: Olvidar fetch-depth: 0
Síntoma: El step "Get PR diff" falla con un mensaje sobre que el commit base no existe.
Por qué pasa: Por defecto, actions/checkout hace shallow clone (solo el último commit). Sin el historial completo, git diff origin/main...HEAD no puede resolver origin/main.
Cómo corregir: Siempre incluir fetch-depth: 0 cuando necesitas comparar contra otra rama.
Error 2: Sintaxis YAML inválida
Síntoma: GitHub muestra "invalid workflow file" antes de ejecutar.
Por qué pasa: YAML es sensible a indentación. Mezclar tabs y espacios, indentar mal un step, o poner dos puntos sin espacio rompe el parser.
Cómo corregir: Usa un editor con linting de YAML (VS Code con la extensión YAML lo hace nativamente). Antes de pushear, valida el archivo con yamllint o el linter integrado de GitHub Actions.
Error 3: Hardcodear la API key "para probar"
Síntoma: El workflow funciona, pero en el historial de git queda ANTHROPIC_API_KEY: sk-ant-real....
Por qué pasa: Es la tentación clásica al hacer debug. "La pongo aquí para que funcione, después la muevo a Secrets". Pero cualquier commit con la key queda público para siempre (incluso si después la rotas).
Cómo corregir: Configura GitHub Secrets desde el primer commit (cápsula 03). Si por accidente comiteas una key, revócala inmediatamente desde la consola de Anthropic y rota.
Error 4: Triggers demasiado amplios
Síntoma: El workflow corre en cada push de cada rama, generando docenas de runs por día y costos no esperados.
Por qué pasa: Configurar on: push sin filtros, o on: [push, pull_request] sin restringir tipos.
Cómo corregir: Empezar con triggers específicos (pull_request: types: [opened, synchronize]) y agregar más solo si hay necesidad real. La cápsula 05 desarrolla estrategias de selectividad.
Error 5: El script Python falla y el workflow muestra "success"
Síntoma: El workflow se marca como exitoso aunque el script falló silenciosamente.
Por qué pasa: El script puede capturar la excepción y no propagar el error. Si pr_diff.txt está vacío o el SDK falla, el script termina con exit code 0 aunque no produjo análisis.
Cómo corregir: Validar exit codes en el script. Usar exit(1) en errores. En el step del workflow, evitar continue-on-error: true excepto cuando es intencional.
Diagnóstico: Verifica Tu Primer Workflow
Pregunta 1: ¿Tu archivo YAML está en `.github/workflows/`?
GitHub solo detecta workflows en esa carpeta. Si lo pusiste en otro lado, no se dispara.
Pregunta 2: ¿Configuraste el secret `ANTHROPIC_API_KEY` en el repo?
Settings → Secrets and variables → Actions → New repository secret. Si no está configurado, el workflow corre pero el script falla con "API key missing". La cápsula 03 desarrolla esto.
Pregunta 3: ¿Probaste el script Python localmente antes de pushear?
Si funciona local, casi seguro funciona en CI. Si nunca lo probaste local, vas a iterar varios push-fail-push antes de que funcione.
Pregunta 4: ¿El workflow corrió cuando abriste el PR?
Si no aparece en "Actions", revisa: (1) que el archivo esté en la rama target del PR, (2) que la sintaxis YAML sea válida, (3) que los triggers on: incluyan pull_request.
Pregunta 5: ¿El log del workflow muestra el análisis de Claude Code?
Si dice "No hay cambios para analizar", el diff salió vacío (probablemente fetch-depth mal). Si muestra error de API, revisa el secret. Si muestra el análisis: el primer workflow funciona.
Ejercicios
Ejercicio 1: Configurar el primer workflow (Fácil)
Crea los dos archivos (workflow + script Python) en un repositorio de prueba. Configura el secret ANTHROPIC_API_KEY. Abre un PR con un cambio simple. Verifica que el análisis aparece en los logs.
Ver checklist de verificación
-
.github/workflows/claude-code-analysis.ymlcreado en la rama target -
.github/scripts/analyze_pr.pycreado - Secret
ANTHROPIC_API_KEYconfigurado en Settings - PR abierto contra la rama que tiene los archivos
- Workflow run aparece en pestaña "Actions"
- Logs muestran el análisis con texto del modelo
Ejercicio 2: Cambiar el trigger (Medio)
Modifica el workflow para que corra también cuando se agrega la label needs-review al PR (no solo en opened/synchronize). Verifica que funciona agregando la label manualmente.
Ver solución
on:
pull_request:
types: [opened, synchronize, labeled]
jobs:
analyze:
runs-on: ubuntu-latest
if: github.event.action != 'labeled' || github.event.label.name == 'needs-review'
steps:
# ... resto igual
El if filtra: si la action es labeled, solo continúa cuando la label es needs-review. Para opened y synchronize, el workflow corre siempre.
Ejercicio 3: Manejo robusto de errores (Medio)
Modifica el script Python para que:
- Falle con exit code 1 si el diff está vacío (en lugar de exit 0).
- Capture errores de la API y los muestre claramente antes de terminar con exit 1.
Ver solución
import os, sys
from pathlib import Path
from anthropic import Anthropic, APIError
try:
diff_text = Path("pr_diff.txt").read_text()
except FileNotFoundError:
print("ERROR: pr_diff.txt no encontrado.", file=sys.stderr)
sys.exit(1)
if not diff_text.strip():
print("ERROR: el diff está vacío. Revisa fetch-depth.", file=sys.stderr)
sys.exit(1)
try:
client = Anthropic()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=2000,
messages=[{"role": "user", "content": f"Analiza este diff:\n{diff_text}"}],
)
print(response.content[0].text)
except APIError as e:
print(f"ERROR de API: {e}", file=sys.stderr)
sys.exit(1)
Resumen
- Workflow YAML vive en
.github/workflows/y se dispara según loson:configurados - Triggers específicos (no
on: pushsin filtro) ahorran costos fetch-depth: 0es necesario para comparar contra otra rama- El SDK headless se invoca desde un script Python externo, no inline en el YAML
- Probar local antes de pushear ahorra ciclos de iteración
- Validar exit codes evita que el workflow muestre "success" cuando el script falló silenciosamente
Próxima cápsula: 03 — Secrets management con GitHub Secrets. La parte que no negociamos: cómo manejar tu API key sin exponerla, en qué nivel configurar el secret (repo / org / environment), y qué hacer si por accidente la commiteas.
Recursos Adicionales
- GitHub Actions Workflow Syntax — Referencia oficial completa
- actions/checkout — Action oficial de checkout
- actions/setup-python — Action oficial de setup Python
- Anthropic Python SDK — SDK oficial
- GitHub Actions Pricing — Costos de minutos de runner
- yamllint — Linter de YAML para validar antes de pushear