| name | diagnosing-bugs |
| description | Bucle de diagnóstico para bugs difíciles y regresiones de rendimiento. Usar cuando el usuario diga "diagnostica"/"debuggea esto", o reporte algo roto, que lanza errores, que falla o que va lento. |
Diagnosticar bugs
Una disciplina para bugs difíciles. Saltarse fases solo cuando esté explícitamente justificado.
Al explorar el codebase, leer CONTEXT.md (si existe) para obtener un modelo mental claro de los módulos relevantes, y revisar los ADRs del área que se está tocando.
Fase 1 — Construir un bucle de feedback
Esto es el skill. Todo lo demás es mecánico. Si tienes una señal pass/fail ajustada para el bug — una que se ponga red con este bug — encontrarás la causa; la bisección, la prueba de hipótesis y la instrumentación no hacen más que consumirla. Si no la tienes, ninguna cantidad de mirar código te salvará.
Dedica aquí un esfuerzo desproporcionado. Sé agresivo. Sé creativo. Niégate a rendirte.
Maneras de construir uno — probarlas aproximadamente en este orden
- Test que falla en el seam que alcance el bug — unit, integration, e2e.
- Script de curl / HTTP contra un dev server corriendo.
- Invocación de CLI con un fixture de entrada, comparando stdout contra un snapshot conocido-bueno.
- Script de navegador headless (Playwright / Puppeteer) — maneja la UI, hace aserciones sobre DOM/console/red.
- Reproducir una traza capturada. Guardar en disco una petición de red real / payload / log de eventos; reproducirla a través del code path en aislamiento.
- Harness desechable. Levantar un subconjunto mínimo del sistema (un servicio, dependencias mockeadas) que ejercite el code path del bug con una sola llamada a función.
- Bucle de property / fuzz. Si el bug es "a veces la salida es incorrecta", ejecutar 1000 entradas aleatorias y buscar el modo de fallo.
- Harness de bisección. Si el bug apareció entre dos estados conocidos (commit, dataset, versión), automatizar "arrancar en el estado X, comprobar, repetir" para poder hacer
git bisect run.
- Bucle diferencial. Pasar la misma entrada por la versión vieja vs la nueva (o dos configuraciones) y comparar salidas.
- Script bash HITL. Último recurso. Si un humano debe hacer clic, dirígelo a él con hitl-loop.template.sh para que el bucle siga siendo estructurado. La salida capturada vuelve a ti.
Construye el bucle de feedback correcto y el bug está resuelto al 90%.
Ajustar el bucle
Trata el bucle como un producto. En cuanto tengas un bucle, ajústalo:
- ¿Puedo hacerlo más rápido? (Cachear el setup, saltar inicialización no relacionada, estrechar el alcance del test.)
- ¿Puedo afilar la señal? (Asertar sobre el síntoma específico, no "no crasheó".)
- ¿Puedo hacerlo más determinista? (Fijar el tiempo, seedear el RNG, aislar el filesystem, congelar la red.)
Un bucle flaky de 30 segundos es apenas mejor que ningún bucle; uno determinista de 2 segundos es ajustado — un superpoder de debugging.
Bugs no deterministas
El objetivo no es una repro limpia sino una tasa de reproducción más alta. Ejecuta el trigger en bucle 100×, paraleliza, añade estrés, estrecha ventanas de timing, inyecta sleeps. Un bug que falla el 50% de las veces es debuggeable; el 1% no — sigue subiendo la tasa hasta que sea debuggeable.
Cuando genuinamente no puedes construir un bucle
Detente y dilo explícitamente. Lista lo que intentaste. Pide al usuario: (a) acceso al entorno que lo reproduce, (b) un artefacto capturado (archivo HAR, volcado de logs, core dump, grabación de pantalla con timestamps), o (c) permiso para añadir instrumentación temporal en producción. No procedas a hipotetizar sin un bucle.
Criterio de completitud — un bucle ajustado que se pone red
La Fase 1 está lista cuando el bucle es ajustado y capaz de ponerse red: puedes nombrar un comando — la ruta de un script, la invocación de un test, un curl — que ya has ejecutado al menos una vez (pega la invocación y su salida), y que es:
Si te sorprendes leyendo código para armar una teoría antes de que este comando exista, para — saltar directo a una hipótesis es exactamente el fallo que este skill previene. Sin comando capaz de ponerse red, no hay Fase 2.
Fase 2 — Reproducir + minimizar
Ejecuta el bucle. Míralo ponerse red — el bug aparece.
Confirma:
Minimizar
En cuanto esté red, encoge la repro al escenario más pequeño que siga poniéndose red. Recorta entradas, callers, configuración, datos y pasos de uno en uno, re-ejecutando el bucle tras cada recorte — conserva solo lo que sostiene el fallo.
Por qué molestarse: una repro mínima encoge el espacio de hipótesis de la Fase 3 (quedan menos piezas móviles que sospechar) y se convierte en el test de regresión limpio de la Fase 5.
Está listo cuando cada elemento restante sostiene el fallo — quitar cualquiera de ellos hace que el bucle se ponga green.
No avances hasta haber reproducido y minimizado.
Fase 3 — Hipotetizar
Genera 3–5 hipótesis rankeadas antes de probar ninguna. Generar una sola hipótesis ancla en la primera idea plausible.
Cada hipótesis debe ser falsable: enuncia la predicción que hace.
Formato: "Si es la causa, entonces hará desaparecer el bug / lo empeorará."
Si no puedes enunciar la predicción, la hipótesis es una corazonada — descártala o afílala.
Muestra la lista rankeada al usuario antes de probar. A menudo tiene conocimiento de dominio que re-rankea al instante ("acabamos de desplegar un cambio en la #3"), o conoce hipótesis que ya descartó. Checkpoint barato, gran ahorro de tiempo. No te bloquees en él — continúa con tu ranking si el usuario está ausente.
Fase 4 — Instrumentar
Cada sonda debe mapear a una predicción específica de la Fase 3. Cambia una variable por vez.
Preferencia de herramientas:
- Debugger / inspección en REPL si el entorno lo soporta. Un breakpoint vale más que diez logs.
- Logs dirigidos en los límites que distinguen hipótesis.
- Nunca "loguear todo y grepear".
Etiqueta cada log de debug con un prefijo único, p. ej. [DEBUG-a4f2]. La limpieza final se vuelve un solo grep. Los logs sin etiqueta sobreviven; los etiquetados mueren.
Rama de rendimiento. Para regresiones de rendimiento, los logs suelen ser la herramienta equivocada. En su lugar: establece una medición de línea base (harness de timing, performance.now(), profiler, query plan) y luego bisecta. Medir primero, arreglar después.
Fase 5 — Fix + test de regresión
Escribe el test de regresión antes del fix — pero solo si existe un seam correcto para él.
Un seam correcto es uno donde el test ejercita el patrón real del bug tal como ocurre en el call site. Si el único seam disponible es demasiado superficial (test de un solo caller cuando el bug necesita varios, unit test que no puede replicar la cadena que disparó el bug), un test de regresión ahí da falsa confianza.
Si no existe un seam correcto, eso mismo es el hallazgo. Anótalo. La arquitectura del codebase está impidiendo dejar el bug bajo llave. Márcalo para la fase siguiente.
Si existe un seam correcto:
- Convierte la repro minimizada en un test que falla en ese seam.
- Míralo fallar.
- Aplica el fix.
- Míralo pasar.
- Re-ejecuta el bucle de feedback de la Fase 1 contra el escenario original (sin minimizar).
Fase 6 — Limpieza + post-mortem
Requerido antes de declarar terminado:
Después pregunta: ¿qué habría prevenido este bug? Si la respuesta implica cambio arquitectónico (sin buen seam de test, callers enredados, acoplamiento oculto), traspásalo al skill /improve-codebase-architecture con los detalles. Haz la recomendación después de que el fix esté dentro, no antes — ahora tienes más información que cuando empezaste.