Módulo 2: Code Review Automático en PRs
Review Summary y Suggest-Only Mode
Review Summary y Suggest-Only Mode
Descripción
Tienes inline comments funcionando (cápsula 03). Pero un review profesional incluye dos cosas más: un summary que da contexto general antes de los comments inline, y una decisión consciente sobre si el bot bloquea merges o solo sugiere. Esta cápsula cubre ambas.
El summary es lo primero que el developer ve cuando abre el PR. Es donde el bot dice "encontré 3 issues, los principales son X y Y, en general el cambio es bueno/preocupante/neutral". Sin summary, los inline comments aparecen en aislamiento — el developer no sabe si son issues críticos o detalles menores.
El modo de operación (suggest-only vs bloqueo) es una decisión organizacional más que técnica. Vas a aprender por qué empezar en suggest-only es casi siempre lo correcto, cómo evolucionar gradualmente hacia bloqueo selectivo, y qué métricas usar para tomar la decisión basada en datos.
Anatomía de un Review Profesional
┌────────────────────────────────────────────────┐
│ 🤖 Code Review (Claude Code) │
├────────────────────────────────────────────────┤
│ │
│ ## Resumen │ ← summary
│ Este PR refactoriza el flujo de payment para │
│ extraer la lógica de validación a un service. │
│ La estructura general es sólida, pero hay 2 │
│ issues que vale la pena resolver antes del │
│ merge: │
│ │
│ - 🚨 SQL injection potencial en auth.py:87 │
│ - ⚠️ Falta validación de tipo en payment.py:42 │
│ │
│ Otras observaciones menores como sugerencias │
│ inline. │
│ │
│ ## Severidad │
│ - 1 critical │
│ - 1 warning │
│ - 3 suggestions │
│ │
│ --- │
│ Tokens: 1,250 in / 380 out │
└────────────────────────────────────────────────┘
[+ inline comments en cada archivo afectado]
El summary responde tres preguntas que el developer se hace al abrir el PR:
- ¿En general, este cambio está bien o tiene problemas serios?
- ¿Cuántos issues críticos hay?
- ¿Vale la pena leer cada inline comment o son detalles menores?
Generar el Summary con el Mismo Prompt
El prompt que pide al modelo el JSON estructurado (cápsula 03) ya incluía un campo summary. Acá lo desarrollamos para que tenga forma profesional.
Prompt mejorado
prompt = f"""Haz code review de este diff de pull request.
Devuelve un JSON con esta estructura:
{{
"summary": {{
"overview": "1-2 párrafos describiendo qué cambia y la calidad general",
"highlights": ["punto 1 del summary", "punto 2", "..."],
"verdict": "ready_to_merge|needs_minor_changes|needs_major_changes"
}},
"comments": [
{{
"path": "...",
"line": N,
"severity": "critical|warning|suggestion",
"body": "..."
}}
]
}}
Reglas:
- `overview` debe contextualizar (no solo enumerar)
- `highlights` son los 2-4 puntos más importantes (no todos)
- `verdict` es la opinión del bot sobre el merge
- Solo `critical` para bugs reales o vulnerabilidades de seguridad
- Devuelve SOLO el JSON
Diff:
{diff_text}
"""
Construir el body del summary comment
def build_summary_markdown(review_data):
"""Genera el markdown del summary del review."""
summary = review_data["summary"]
comments = review_data["comments"]
# Conteo por severidad
by_severity = {"critical": 0, "warning": 0, "suggestion": 0}
for c in comments:
by_severity[c.get("severity", "suggestion")] += 1
# Verdict con emoji
verdict_emoji = {
"ready_to_merge": "✅",
"needs_minor_changes": "⚠️",
"needs_major_changes": "🚨",
}.get(summary["verdict"], "💬")
body = f"""# 🤖 Code Review (Claude Code)
## Resumen
{summary["overview"]}
### Puntos clave
"""
for h in summary["highlights"]:
body += f"- {h}\n"
body += f"""
### Veredicto
{verdict_emoji} **{summary["verdict"].replace("_", " ").title()}**
### Severidad
- 🚨 Critical: {by_severity['critical']}
- ⚠️ Warning: {by_severity['warning']}
- 💡 Suggestion: {by_severity['suggestion']}
Detalles inline en los archivos afectados.
"""
return body
Publicarlo como parte del review
El review API (cápsula 03) tiene un campo body para el comment general. Ese es donde va el summary:
review_payload = {
"commit_id": commit_sha,
"body": build_summary_markdown(review_data), # ← summary general
"event": "COMMENT",
"comments": valid_comments, # ← inline comments
}
Resultado: un review con summary al inicio + comments inline. Una sola llamada API, output completo.
Suggest-Only Mode: La Configuración por Defecto
Suggest-only mode significa que el bot no bloquea merges, solo comenta. Es la configuración correcta para 99% de los casos al inicio.
Por qué empezar acá
SI el bot bloquea desde el día 1:
→ Genera 5 comments en el primer PR del equipo
→ 2 son falsos positivos (el bot no entendía un patrón legítimo)
→ Developer frustrado: "el bot está bloqueando algo que está bien"
→ Solicita override
→ Override genera fricción
→ Después de 3 PRs así, alguien desactiva el bot
→ Resultado: no hay bot, no hay value
SI el bot opera en suggest-only:
→ Comenta lo mismo
→ Los falsos positivos se ignoran (sin bloqueo)
→ Los issues reales se ven y se accionan
→ Equipo gana confianza gradualmente
→ Después de 4-6 semanas: el equipo ya ve cuándo el bot acierta
→ Momento de evolucionar a bloqueo selectivo (próxima sección)
Configurar suggest-only
Es la configuración default del review API. El campo event: "COMMENT" no requiere cambios:
review_payload = {
"commit_id": commit_sha,
"body": summary_markdown,
"event": "COMMENT", # ← suggest-only: comenta sin bloquear
"comments": valid_comments,
}
COMMENT aparece como un review de tipo "Comment" en GitHub. No marca el PR como "approved" ni "changes requested".
Evolucionar a Bloqueo Selectivo
Después de varias semanas operando en suggest-only, puedes tener métricas de qué tan bien funciona el bot:
- True positive rate: X% de los issues marcados como critical
son confirmados como bugs reales por code review humano
- False positive rate: Y% de issues marcados como critical
son rechazados (el bot se equivocó)
- Precision por severidad: cuánto acierta en critical vs warning
Cuando la precision en critical es >95%, puedes evolucionar a bloquear solo en críticos:
# Decidir el event basado en severity más alta encontrada
has_critical = any(c.get("severity") == "critical" for c in valid_comments)
review_payload = {
"commit_id": commit_sha,
"body": summary_markdown,
"event": "REQUEST_CHANGES" if has_critical else "COMMENT",
"comments": valid_comments,
}
REQUEST_CHANGES marca el PR como "changes requested". Si el repo tiene branch protection con "require approvals" + "dismiss stale reviews", el merge se bloquea hasta que se resuelva.
Branch protection complementaria
Para que REQUEST_CHANGES realmente bloquee:
- Settings → Branches → Branch protection rules → main
- Activar "Require a pull request before merging"
- "Require approvals: 1"
- "Dismiss stale pull request approvals when new commits are pushed" (importante)
- "Require review from Code Owners" (opcional pero recomendable)
Sin esta configuración, REQUEST_CHANGES solo marca pero no bloquea — quien sea con write access puede mergear igual.
Mecanismo de Override
Aún con bloqueo selectivo, necesitas un mecanismo de override para los casos donde el bot se equivoca pero el equipo quiere mergear igual.
Opción 1: Approval humano supera REQUEST_CHANGES
GitHub permite que un review aprobatorio humano dismiss el REQUEST_CHANGES del bot. El developer:
- Ve el bot bloquear
- Confirma manualmente que es falso positivo
- Pide review humano
- Reviewer humano aprueba (esto sobreescribe el del bot)
- Merge habilitado
Es el mecanismo más natural — mantiene al humano en el loop.
Opción 2: Label de override
Permite a developers con autoridad agregar una label override-bot-review que skipea el bloqueo:
- name: Run review
if: |
!contains(github.event.pull_request.labels.*.name, 'override-bot-review')
# ...
Útil cuando:
- El equipo es chico y todos tienen autoridad
- Hay urgencia (hotfix de producción)
- El bot tiene un bug temporal
Riesgo: la label se vuelve "siempre presente". Monitorear su uso para detectar cuándo el bot necesita ajustes.
Opción 3: [skip ai] en commit message
- name: Run review
if: "!contains(github.event.head_commit.message, '[skip ai]')"
Más casual, pero sirve para casos rápidos. Mismo riesgo de overuse.
Cuándo NO Bloquear
Algunas categorías de findings nunca deberían bloquear, incluso si el bot tiene alta precision:
| Categoría | Por qué no bloquear |
|---|---|
| Estilo / formato | Hay linters dedicados (Black, Prettier) |
| Naming preferences | Subjetivo, varía por equipo |
| Sugerencias de refactoring | Mejora, no corrección |
| Documentation gaps | No es bug, es deuda |
| Performance optimizations | Premature, salvo casos extremos |
Solo bloquear en:
- Bugs claros (lógica incorrecta, null pointer, off-by-one)
- Vulnerabilidades de seguridad (SQL injection, XSS, secrets)
- Breaking changes no documentados
Trampas Comunes
Error 1: Bloquear desde el día 1
Síntoma: Developers frustrados, override de la label se vuelve default, eventualmente desactivan el bot.
Por qué pasa: "Si el bot encuentra problemas, hay que bloquear". Pero antes de tener confianza, el bot tiene falsos positivos.
Cómo corregir: Empezar siempre en suggest-only. Medir precision durante 4-6 semanas. Evolucionar a bloqueo solo en categorías donde precision >95%.
Error 2: Summary sin contexto
Síntoma: El summary es solo "encontré 3 issues" sin describir qué cambia o el verdict general.
Por qué pasa: El prompt no pide overview ni verdict explícitos.
Cómo corregir: Estructurar el JSON del summary con overview, highlights, verdict (sección "Generar el Summary con el Mismo Prompt").
Error 3: REQUEST_CHANGES sin branch protection
Síntoma: El bot marca "changes requested" pero el merge sigue habilitado.
Por qué pasa: Sin "Require approvals" + "Dismiss stale" en branch protection, el REQUEST_CHANGES es decorativo.
Cómo corregir: Configurar branch protection antes de habilitar el bloqueo.
Error 4: No tener override mechanism
Síntoma: El bot bloquea por error, equipo no puede mergear, productividad cae.
Por qué pasa: Activaron bloqueo sin pensar en cómo desbloquear cuando el bot se equivoca.
Cómo corregir: Documentar y comunicar el mecanismo de override antes de activar bloqueo. Approval humano + label de emergencia.
Error 5: Métricas no medidas
Síntoma: El equipo discute si el bot funciona bien sin datos.
Por qué pasa: No hay tracking de qué tan seguido los critical findings son confirmados o rechazados.
Cómo corregir: Agregar a artifacts (cápsula 04 del módulo 1) un JSON con cada finding y su severity. Periódicamente revisar y calcular precision.
Diagnóstico
Pregunta 1: ¿Tu bot opera en suggest-only o ya bloquea?
Si bloquea sin haber medido precision >95% en critical, probablemente generas más fricción que valor.
Pregunta 2: ¿Tu summary tiene overview, highlights y verdict?
Si solo enumera issues, el developer no tiene contexto general. La estructura overview+highlights+verdict da el panorama de un vistazo.
Pregunta 3: ¿Tu repo tiene branch protection que respeta REQUEST_CHANGES?
Sin esto, el bot bloqueando es teatro. Configurar antes de activar bloqueo.
Pregunta 4: ¿Documentaste el mecanismo de override?
Si no, el primer falso positivo serio va a generar caos. Documentar en .github/CONTRIBUTING.md o equivalente.
Pregunta 5: ¿Estás guardando datos para medir precision?
Sin datos, la decisión "evolucionar a bloqueo" es opinión. Con datos, es justificable.
Ejercicios
Ejercicio 1: Implementar el summary completo (Medio)
Modifica el script para que el review payload incluya un summary con overview + highlights + verdict (no solo lista de issues).
Ver solución
Ver función build_summary_markdown en la sección "Construir el body del summary comment".
Ejercicio 2: Configurar suggest-only y verificar (Fácil)
Confirma que tu bot opera con event: "COMMENT". Verifica en un PR de prueba que el review aparece como "Comment" type, no como "Approved" ni "Changes requested".
Ejercicio 3: Evolución a bloqueo selectivo con override (Difícil)
Implementa:
- El review usa
REQUEST_CHANGESsolo si hay critical findings - Hay un mecanismo de override con label
override-bot-review - Branch protection configurada para que REQUEST_CHANGES bloquee real
Documenta el override mechanism en CONTRIBUTING.md.
Resumen
- Summary da contexto general — overview, highlights, verdict
- Suggest-only mode es el punto de partida correcto en 99% de casos
- Evolución a bloqueo debe basarse en métricas: precision >95% en critical
- Branch protection es prerequisito para que
REQUEST_CHANGESbloquee - Override mechanism es prerequisito para bloqueo (approval humano, label,
[skip ai]) - Solo bloquear en bugs claros y vulnerabilidades — no en estilo o subjetividades
Próxima cápsula: 05 — Personalizar convenciones del equipo. Tienes un bot funcional que comenta y operas conscientemente sobre cuándo bloquea. Falta lo que separa un bot genérico de uno invaluable: que aplique las convenciones específicas de tu proyecto.
Recursos Adicionales
- GitHub Branch Protection Rules — Setup de protection rules
- GitHub Reviews API: events — Tipos de event (COMMENT, APPROVE, REQUEST_CHANGES)
- GitHub: Required reviews — Configurar approvals requeridos
- GitHub Code Owners — Asignar reviewers automáticos
- Anthropic: Structured outputs — Forzar JSON del modelo
- Google's "Code Review at Google" — Cultura de code review en Google (referencia para diseñar la del equipo)