EN · Read in English Explorar gratis

Receta · Intermedio · 15 min

Depurar un bug con Claude Code: del error al test de regresión

Un flujo en seis pasos para depurar con Claude Code: reproducir, pasar la evidencia, hipótesis sin editar, test que falla primero y arreglo mínimo.

Portada: Depurar un bug con Claude Code: del error al test de regresión

Probado con Claude Code 2.1.316 · revisado

Ver los archivos en GitHub Gratis · licencia MIT

El problema

Pedirle a Claude Code «arréglalo» suele acabar en un parche del síntoma, un test retocado para que pase o un bug que vuelve a la semana. Con un «arréglalo» Claude solo sabe que algo va mal y tiene que adivinar qué, dónde y cómo comprobar que quedó bien. Esta receta da un flujo en seis pasos que termina con un test de regresión que impide que el bug vuelva, con un ejemplo real: cuotas que suman 99.99 en vez de 100.00.

La solución

La guía oficial de buenas prácticas lo explica con un ejemplo: en vez de «fix the login bug», escribe «los usuarios dicen que el login falla tras expirar la sesión; mira el refresco del token en src/auth/, escribe un test que falle y lo reproduzca, y luego arréglalo». La diferencia es información que Claude no puede leer en el código: el síntoma observado, dónde mirar y qué significa «arreglado». Los consejos oficiales para bugs son tres: dale el comando que lo reproduce y el stack trace, cuéntale los pasos para reproducirlo y dile si el fallo es constante o intermitente. Los pasos siguientes convierten esos consejos en un flujo repetible.

1. Reprodúcelo antes de tocar nada

Un bug que no puedes reproducir no lo puedes dar por arreglado. Antes de abrir Claude, consigue un comando que lo dispare: un test que ya falla, un curl contra tu API local, un script de cinco líneas o la secuencia de clics exacta. Anota el resultado esperado y el que obtienes, con datos concretos («100.00 en 3 cuotas suma 99.99»), no «sale mal».

Si es intermitente, ejecútalo varias veces y apunta la proporción («falla 1 de cada 10»): cambia por completo las hipótesis (carreras, caché, hora, orden de tests). Si sabes desde cuándo pasa, di el commit o la versión; git log y git bisect son pistas que Claude aprovecha muy bien. Si aún no tienes forma de reproducirlo, ese es el primer encargo: «ayúdame a escribir un script que reproduzca esto, sin arreglar nada».

Nuestro ejemplo, cuotas.py, redondea cada cuota con float:

def dividir_en_cuotas(total: str, n: int) -> list[float]:
    cuota = round(float(total) / n, 2)
    return [cuota] * n

dividir_en_cuotas("100.00", 3) devuelve tres cuotas de 33.33, que suman 99.99.

2. Pásale la evidencia, no tu teoría

Dale a Claude lo que ya tienes en lugar de describirlo. Para un archivo, escribe @ y la ruta (@logs/error.log) y su contenido entra en el mensaje. Para un error en pantalla, pega la captura en el prompt. Para la salida de un comando, la doc oficial propone canalizarla desde la shell:

cat logs/error.log | claude -p "¿qué está pasando aquí? dame hipótesis, no edites nada"

Pega el stack trace completo, no la última línea: las rutas y números de línea de en medio son las que señalan el origen. Evita adelantar tu sospecha («creo que es la caché»): si la dices, Claude tiende a buscar donde le señalas. Mejor describe el síntoma y deja que forme hipótesis con la evidencia; si tienes una sospecha, añádela al final como una hipótesis más. Un bug por conversación: cuando termines, /clear para que los restos de un bug no contaminen el siguiente.

Esta plantilla junta todo el flujo en un solo mensaje. Cópiala y rellena lo que va entre < >:

Hay un bug: <síntoma observable, con datos concretos>.
Se reproduce con: <comando exacto> (falla siempre | falla 1 de cada N veces).
Salida/error: @logs/error.log  (o pégalo tal cual, con el stack trace completo)
Esperaba: <resultado correcto>. Obtengo: <resultado actual>.
Desde cuándo: <commit, versión o cambio sospechoso, si lo sabes>.

Cómo quiero que trabajes:
1. Reprodúcelo tú primero con el comando y enséñame la salida.
2. Dame 2-3 hipótesis ordenadas por probabilidad y cómo descartarías cada una. No edites nada todavía.
3. Si hace falta, añade logs temporales marcados con DEBUG-CLAUDE y ejecuta de nuevo.
4. Escribe un test de regresión que falle por este bug y enséñame que falla.
5. Haz el cambio mínimo que lo ponga en verde. No modifiques ni relajes el test.
6. Ejecuta toda la suite, quita los logs DEBUG-CLAUDE y resume la causa raíz en 3 líneas.

3. Hipótesis en modo plan, sin editar

Antes de que cambie una línea, pídele que piense. /plan entra en modo plan desde el prompt y acepta una descripción, por ejemplo /plan la suma de cuotas no cuadra, ver bug #42. En modo plan Claude lee y propone, pero no edita tu código hasta que apruebas el plan. Pídele dos o tres hipótesis ordenadas por probabilidad y, para cada una, qué observación la confirmaría o la descartaría. Así ves si entendió el problema antes de gastar tiempo en un arreglo equivocado.

Si el bug está en código que no escribiste, en un test inestable o en un cambio de esquema con muchos lectores, la guía oficial recomienda subir el esfuerzo con /effort high o /effort xhigh: Claude revisa más casos límite y verifica más su trabajo, a cambio de más tiempo y tokens. Para un bug sencillo, el esfuerzo por defecto basta.

4. Logs temporales que se puedan borrar

Cuando la lectura del código no basta, la forma rápida de descartar hipótesis es observar: imprimir valores intermedios, tiempos o el orden de las llamadas. Pide que cada log temporal lleve una marca fija, por ejemplo un comentario o prefijo DEBUG-CLAUDE, para encontrarlos y quitarlos al final con grep -rn "DEBUG-CLAUDE" ..

Que Claude ejecute el comando de reproducción y lea la salida él mismo: es más fiable que copiar y pegar a mano. Si la salida es enorme (miles de líneas de log o una suite lenta), delega esa parte en un subagente: lee todo en su propio contexto y solo devuelve el resumen, así la conversación principal no se llena. Por defecto Claude te pedirá permiso para ejecutar comandos; con el modo automático o una regla allow para tu comando de tests (Bash(python3 -m unittest *), ver permisos) el ciclo es mucho más fluido.

5. Test de regresión en rojo, arreglo mínimo en verde

Este es el paso que hace que el bug no vuelva. Pide primero un test que reproduzca exactamente el fallo y que Claude te enseñe que falla (rojo). Si el test pasa con el código roto, no prueba nada. Después, el cambio mínimo que lo pone en verde, y la suite completa para comprobar que no rompió otra cosa. Dilo explícito en el prompt: «no modifiques ni relajes el test». Un fallo habitual de los agentes es aflojar la aserción o reescribir el test hasta que pasa.

El test de regresión del ejemplo, test_cuotas.py, compara la suma con Decimal("100.00"). Nombra el test con el número del bug para que se entienda en el futuro:

import unittest
from decimal import Decimal

from cuotas import dividir_en_cuotas


class TestCuotas(unittest.TestCase):
    def test_regresion_bug_42_suma_exacta(self):
        # Bug #42: 100.00 en 3 cuotas sumaba 99.99
        cuotas = dividir_en_cuotas("100.00", 3)
        self.assertEqual(sum(Decimal(str(c)) for c in cuotas), Decimal("100.00"))

    def test_division_exacta_no_cambia(self):
        cuotas = dividir_en_cuotas("90.00", 3)
        self.assertEqual([Decimal(str(c)) for c in cuotas], [Decimal("30.00")] * 3)


if __name__ == "__main__":
    unittest.main()

El arreglo trabaja con Decimal, redondea hacia abajo y suma los céntimos sobrantes a la primera cuota (33.34 + 33.33 + 33.33):

from decimal import Decimal, ROUND_DOWN


def dividir_en_cuotas(total: str, n: int) -> list[Decimal]:
    """Divide un importe en n cuotas de 2 decimales que suman exactamente el total."""
    if n < 1:
        raise ValueError("n debe ser >= 1")
    total_d = Decimal(total)
    base = (total_d / n).quantize(Decimal("0.01"), rounding=ROUND_DOWN)
    cuotas = [base] * n
    # El resto (céntimos que se pierden al redondear) va a la primera cuota
    cuotas[0] += total_d - base * n
    return cuotas

6. Revisa, limpia y deja constancia

Antes del commit, revisa tú el git diff: que el cambio sea pequeño, que no queden logs DEBUG-CLAUDE y que el test nuevo esté incluido. /code-review (skill incluida) revisa el diff actual en busca de errores de corrección si quieres una segunda opinión. Pide a Claude un resumen de la causa raíz en tres líneas y úsalo en el mensaje del commit o del PR.

Ojo con /rewind: deshace los cambios que Claude hizo con sus herramientas de edición, pero un archivo que solo cambiaron comandos de Bash no está rastreado y se queda como está. Por eso conviene hacer commit (o al menos git stash) antes de empezar a depurar: git es tu red de seguridad real. Si el mismo tipo de bug aparece dos veces, conviértelo en regla: una línea en CLAUDE.md («los importes van siempre en Decimal») o un hook que ejecute los tests tras cada edición.

Compruébalo

Con el cuotas.py con bug, el test de regresión tiene que fallar; con el arreglo, pasar:

# Rojo: con el código con bug, el test de regresión falla
python3 -m unittest -q
# AssertionError: Decimal('99.99') != Decimal('100.00')
# FAILED (failures=1)

# Verde: tras el arreglo
python3 -m unittest -q
# OK

# Antes del commit: que no quede ningún log temporal
git diff
grep -rn "DEBUG-CLAUDE" . && echo "Quedan logs temporales"

Lo probamos el 1-nov-2026 en Linux con Python 3.12.3 y Claude Code 2.1.316: el test da exactamente ese AssertionError con el código con bug y OK con el arreglo. Las funciones de Claude Code que citamos (/plan con descripción, /effort, /code-review, /debug, los límites de /rewind y cat … | claude -p) salen de la documentación oficial; aún no hemos grabado una sesión interactiva completa con la plantilla de prompt. Los archivos del ejemplo están en el repositorio ctrlia-depurar-bug.

Por qué funciona

Claude rinde mucho mejor cuando puede comprobar su propio trabajo: un comando que reproduce el fallo y un test que pasa de rojo a verde le dan una señal objetiva de «arreglado», en lugar de su propia opinión. Separar las hipótesis (modo plan, sin editar) del arreglo evita que se lance al primer parche, y el test de regresión queda en la suite para que el bug no vuelva sin que nadie se entere. Más detalles en las buenas prácticas, los flujos de trabajo comunes, la lista de comandos y los checkpoints.

Errores típicos

Preguntas frecuentes

¿El comando /debug sirve para depurar mi código?

No. /debug es una skill incluida que activa el registro de depuración de la sesión y analiza ese registro para diagnosticar problemas de Claude Code mismo (una herramienta que falla, una configuración que no carga). Para un bug de tu programa usa el flujo de esta receta.

¿Qué hago si Claude cambia el test para que pase?

Prohíbelo en el prompt («no modifiques ni relajes el test»), pide que te enseñe el test en rojo antes de arreglar y revisa el diff del archivo de test. Si se repite, añádelo a tu CLAUDE.md como regla del proyecto.

¿Y si el bug es intermitente?

Dilo y da la proporción («falla 1 de cada 10»). Pide hipótesis de concurrencia, orden de tests, caché o tiempo, y un script que lo ejecute en bucle (por ejemplo 50 veces) para medir antes y después del arreglo.

¿Puedo pasarle un log enorme?

Sí, pero mejor con @ruta o canalizado con cat log | claude -p y pidiendo que filtre. Si es muy grande, que un subagente lo lea y te devuelva solo lo relevante, para no llenar el contexto principal.

¿/rewind deshace todo lo que hizo Claude durante la depuración?

No todo. Deshace las ediciones hechas con sus herramientas de edición; los archivos que solo cambiaron comandos de Bash y los cambios externos no se rastrean. Haz commit antes de empezar.

¿Tengo que aprobar cada ejecución de los tests?

En modo Manual sí. Añade una regla allow para tu comando de tests o usa el modo automático, que aprueba lo rutinario y bloquea lo arriesgado (ver la referencia del modo automático y la receta de permisos).

Toda la biblioteca