7 incidentes reales en producción con Claude Code: recuperación completa con RCA y prevención
7 incidentes reales en producción con Claude Code: filtración de claves API, borrado de BD, explosión de facturación y caídas del servicio — con análisis de causa raíz y estrategias de prevención.
“Claude Code es práctico, pero me da miedo usarlo en producción” — muchos desarrolladores comparten esta sensación. Y ese instinto es correcto.
Claude Code opera en tu sistema de archivos y shell con privilegios más elevados que un IDE convencional. Al mismo tiempo, hacer clic repetidamente en el botón de aprobar reduce la vigilancia — una vulnerabilidad psicológica humana bien conocida. Cuando estos dos factores se combinan, los incidentes en producción ocurren.
Este artículo documenta 7 incidentes reales en producción con Claude Code, con causas, alcance del impacto, procedimientos de recuperación, RCA (análisis de causa raíz) y estrategias de prevención. Léelo antes de que ocurra un accidente en tu organización.
Incidente 1: Filtración de clave API → 2.800 € en cargos no autorizados
Línea de tiempo
09:12 Se instruyó a Claude Code: "hacer commit de .env para pasar variables de entorno a CI"
09:13 git add .env && git push se ejecutó sin aprobación (lista allow demasiado permisiva)
09:14 GitHub Secret Scanning lo detectó y envió notificación por correo
09:31 Rastreador de AWS detectó la clave de OpenAI, comenzó uso no autorizado
11:00 Se confirmaron 2.800 € en cargos en el panel de OpenAI
Procedimiento de recuperación
# Paso 1: Revocar la clave API inmediatamente (máxima prioridad — en 5 minutos)
# → Revocar la clave en el panel de OpenAI / cada servicio
# Paso 2: Eliminar .env completamente del historial de git
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Paso 3: Forzar push de todas las ramas
git push origin --force --all
git push origin --force --tags
# Paso 4: Agregar a .gitignore para prevenir recurrencia
echo ".env" >> .gitignore
git add .gitignore && git commit -m "security: add .env to gitignore"
# Paso 5: Emitir una nueva clave API y configurarla en .env
RCA
- Causa directa:
settings.jsonteníaBash(git add*)en la listaallow, permitiendo ejecución sin confirmación - Causa raíz: La configuración de seguridad se pospuso en favor del código de producto
Prevención
// .claude/settings.json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '\\.env' && echo '🚨 .env detectado! Cancelando.' && exit 1 || exit 0"
}]
}]
}
}
Incidente 2: rm -rf borró todo el proyecto
Línea de tiempo
14:33 Instrucción: "limpiar node_modules y reinstalar"
14:33 Claude Code ejecutó rm -rf node_modules (hasta aquí normal)
14:34 Instrucción de seguimiento "borrar también los archivos de build antiguos" → rm -rf dist/
14:34 Mala interpretación del path: se ejecutó rm -rf dist /src (espacio como separador)
14:35 Directorio src/ completamente eliminado. Archivos de config fuera de git también borrados
14:40 git checkout . restauró los archivos rastreados, pero .env y configs locales perdidos permanentemente
Procedimiento de recuperación
# Los archivos rastreados por git pueden restaurarse
git checkout .
git clean -fd # Eliminar archivos extra
# Buscar archivos eliminados en git stash o reflog
git stash list
git reflog
# Los archivos fuera de git (.env etc.) deben restaurarse desde backup
# → Si no hay backup, reconfigura desde cero
RCA
- Causa directa: El path con espacio no fue correctamente entrecomillado, resultando en
rm -rf dist /src - Causa raíz:
rm -rfestaba enallowen vez deask
Prevención
{
"permissions": {
"deny": ["Bash(rm -rf ~*)", "Bash(rm -rf /*)"],
"ask": ["Bash(rm*)"]
},
"hooks": {
"PreToolUse": [{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Comando de eliminación detectado. Objetivo: $CLAUDE_TOOL_INPUT_COMMAND\nEjecución en 5 segundos. Ctrl+C para cancelar.' && sleep 5"
}]
}]
}
}
Incidente 3: git push --force borró los commits de 3 compañeros
Línea de tiempo
16:00 Instrucción: "hay un conflicto con el remoto. Priorizar local y sobreescribir"
16:01 Se ejecutó git push --force origin main
16:01 ~200 líneas de código que 3 compañeros habían commiteado ese día fueron eliminadas
16:10 Compañero A en Slack: "Oye, mis commits han desaparecido"
16:15 Causa identificada. A y B tenían copias locales, pero C ya había eliminado las suyas — pérdida permanente
Procedimiento de recuperación
# Buscar commits perdidos en reflog (en la máquina que ejecutó el push)
git reflog | head -30
# → Ejemplo: abc1234 HEAD@{3}: commit anterior al --force push
# Restaurar los commits perdidos
git checkout -b recovery abc1234
git push origin recovery
# Fusionar en main y limpiar
git checkout main
git merge recovery --no-ff
git push origin main
RCA
- Causa directa: La intención de resolver un conflicto se interpretó como
--forceen vez de--force-with-lease - Causa raíz: El force push a la rama main no estaba en la lista
deny
Prevención
{
"permissions": {
"deny": [
"Bash(git push --force *main*)",
"Bash(git push --force *master*)",
"Bash(git push -f *main*)"
]
}
}
<!-- CLAUDE.md -->
## Reglas de Git
- `git push --force` está prohibido
- Usar `git push --force-with-lease` para resolver conflictos
- Siempre obtener confirmación del usuario antes de hacer push a main/master
Incidente 4: Migración de BD fallida borró 40.000 registros en producción
Línea de tiempo
10:00 Instrucción: "ejecutar migración para agregar columna phone_number a la tabla users"
10:01 Claude Code generó y ejecutó el script de migración
10:01 Un bug en el script hizo que se ejecutara la migración inversa (con DROP COLUMN)
10:02 La columna users.email (NOT NULL) fue eliminada de producción
10:02 Todas las APIs devolvieron errores 500, servicio completamente caído
10:05 Incidente reconocido, investigación de causa raíz comenzó
10:30 Restauración desde snapshot del día anterior (un día de datos perdidos)
Procedimiento de recuperación
# 1. Poner el servicio en modo mantenimiento inmediatamente
# nginx: return 503; o Vercel: página de mantenimiento
# 2. Verificar estado actual de la BD
psql $DATABASE_URL -c "\d users"
# 3. Restaurar desde backup (para RDS)
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier mydb \
--target-db-instance-identifier mydb-restored \
--restore-time 2026-04-17T23:00:00Z
# 4. Si la columna aún existe, agregarla manualmente
ALTER TABLE users ADD COLUMN email VARCHAR(255);
UPDATE users SET email = '(recuperación requerida)' WHERE email IS NULL;
# 5. Restaurar el servicio
RCA
- Causa directa: El generador del script de migración confundió las migraciones up/down
- Causa raíz: La migración se ejecutó en producción sin probarla en un entorno de staging
Prevención
<!-- CLAUDE.md -->
## Reglas obligatorias para migraciones de BD
1. Siempre probar en entorno de staging antes de aplicar en producción
2. Siempre crear backup manual antes de la migración:
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
3. Los scripts con DROP COLUMN / TRUNCATE / DELETE (sin WHERE) siempre
deben ser confirmados por el usuario antes de ejecutarse
4. Si se usa DATABASE_URL de producción, mostrar '¿Escribiendo en BD de PRODUCCIÓN. ¿Continuar?' antes de ejecutar
Incidente 5: Llamadas API infinitas generaron 750 € en una noche
Línea de tiempo
23:00 Instrucción: "reintentar automáticamente en caso de errores" y se inició procesamiento por lotes
23:01 API externa comenzó a devolver 503
23:01 La lógica de reintento corrió sin límite, golpeando la API cada segundo
07:00 Siguiente mañana, notificación de Anthropic: "El uso se acerca al límite"
07:05 Se encontraron 28.000 llamadas API y 750 € en cargos
Procedimiento de recuperación
# 1. Detener el proceso inmediatamente
pkill -f "node batch-process.js"
# 2. Revisar cargos y contactar al soporte de Anthropic
# → La comunicación sincera puede resultar en reembolso parcial
# 3. Configurar alertas de uso
# Consola de Anthropic → Usage Limits → Establecer alerta de presupuesto mensual
Prevención
// utils/retry.ts — siempre usar esta utilidad
export async function withRetry<T>(
fn: () => Promise<T>,
{ maxAttempts = 3, baseDelayMs = 1000, maxDelayMs = 30000 } = {}
): Promise<T> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === maxAttempts) throw err;
const delay = Math.min(
baseDelayMs * 2 ** (attempt - 1) + Math.random() * 500,
maxDelayMs
);
console.warn(`Retry ${attempt}/${maxAttempts} in ${Math.round(delay)}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error("unreachable");
}
<!-- CLAUDE.md -->
## Reglas para llamadas API
- Máximo 3 reintentos con backoff exponencial obligatorio
- while(true) + llamadas API están estrictamente prohibidas
- Los trabajos por lotes deben tener límites de conteo explícitos
- Ejecutar prueba de humo --dry-run antes de la ejecución en producción
Incidente 6: Dependencias rotas tras el deploy dejaron el servicio caído
Línea de tiempo
15:00 Instrucción: "actualizar paquetes a las últimas versiones"
15:01 npm update ejecutado, package-lock.json cambió significativamente
15:05 Build local exitoso
15:10 Deploy en producción
15:12 Incompatibilidad de versión mayor en dependencias de producción, fallo al iniciar
15:12 Errores 503, servicio completamente caído
15:30 Rollback a la versión anterior completado
Procedimiento de recuperación
# Para Vercel / Cloudflare Pages: reactivar el deploy anterior inmediatamente
# → Rollback con un clic desde el panel
# Revertir código con git revert
git revert HEAD~1
git push
# Restaurar package-lock.json a la versión anterior
git checkout HEAD~1 -- package-lock.json
npm ci # Usa estrictamente package-lock.json
RCA
- Causa directa:
npm updateactualizó una versión mayor, causando un breaking change - Causa raíz: Se omitió la verificación del deploy en el entorno de staging
Prevención
<!-- CLAUDE.md -->
## Reglas de gestión de paquetes
- npm update está prohibido (npm update --save-dev condicionalmente permitido)
- Las actualizaciones de versión mayor de paquetes requieren confirmación del usuario
- Siempre verificar el comportamiento en staging antes de hacer deploy
- Monitorear registros de error durante 5 minutos después del deploy en producción
Incidente 7: Permisos mal configurados expusieron los datos de todos los usuarios
Línea de tiempo
11:00 Instrucción: "agregar lista de usuarios al endpoint de API del panel de administración"
11:05 /api/admin/users implementado sin verificación de autenticación
11:10 Deploy en producción
11:10 Información personal de todos los usuarios accesible para cualquiera
13:30 Herramienta de auditoría de seguridad detectó endpoint no autenticado
13:35 Endpoint deshabilitado inmediatamente
Procedimiento de recuperación
# 1. Deshabilitar el endpoint afectado inmediatamente
# nginx: location /api/admin { return 403; }
# 2. Revisar logs de acceso para determinar el alcance de la exposición
grep "/api/admin/users" /var/log/nginx/access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn
# 3. Notificar a los usuarios afectados (verificar requisitos del RGPD / ley de protección de datos)
# 4. Agregar middleware de autenticación y volver a hacer deploy
RCA
- Causa directa: La instrucción “agregar al panel de administración” no comunicó los requisitos de autenticación
- Causa raíz: Los requisitos de seguridad no estaban documentados en CLAUDE.md
Prevención
<!-- CLAUDE.md -->
## Requisitos de seguridad de API
- Los endpoints /api/admin/* deben siempre implementar autenticación de administrador
- Los endpoints /api/user/* deben siempre implementar autenticación de inicio de sesión
- Solo los endpoints /api/public/* son accesibles sin autenticación
- Al agregar una nueva API, comentar explícitamente el nivel de autenticación requerido
Flujo común de respuesta a incidentes (Plantilla de postmortem)
# Postmortem: [Título del incidente]
## Resumen
- Ocurrió a las: YYYY-MM-DD HH:MM
- Detectado a las: YYYY-MM-DD HH:MM
- Resuelto a las: YYYY-MM-DD HH:MM
- Impacto: (número de usuarios / funciones / duración)
- Severidad: P0/P1/P2/P3
## Línea de tiempo
| Hora | Evento |
|-------|--------|
| HH:MM | Incidente ocurrido |
| HH:MM | Detectado |
| HH:MM | Respuesta iniciada |
| HH:MM | Causa raíz identificada |
| HH:MM | Recuperación completada |
## Causa raíz
- Causa directa:
- Causa raíz:
- Causa agravante:
## Acciones de prevención
| Acción | Responsable | Fecha límite |
|--------|-------------|--------------|
| | | |
## Lecciones aprendidas
Resumen: Configuración mínima para prevenir incidentes
// Copia esto ahora y pégalo en .claude/settings.json
{
"permissions": {
"deny": [
"Bash(rm -rf ~*)",
"Bash(rm -rf /*)",
"Bash(git push --force *main*)",
"Bash(git push --force *master*)",
"Bash(git push -f *main*)",
"Bash(DROP TABLE*)",
"Bash(TRUNCATE *)",
"Bash(curl * | bash)",
"Bash(wget * | sh)"
],
"ask": [
"Write(**)", "Edit(**)",
"Bash(rm*)", "Bash(git commit*)",
"Bash(git push*)", "Bash(*deploy*)",
"Bash(npm install*)", "Bash(*migrate*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{ "type": "command",
"command": "git diff --cached --name-only | grep '\\.env' && echo '🚨 .env detectado! Cancelando.' && exit 1 || exit 0" }]
},
{
"matcher": "Bash(rm*)",
"hooks": [{ "type": "command",
"command": "echo '⚠️ Comando de eliminación detectado. Ejecución en 5 segundos. Ctrl+C para cancelar.' && sleep 5" }]
}
]
}
}
Los incidentes en producción ocurren cuando te saltas los 30 minutos que lleva configurar esto. Los 7 incidentes de este artículo podrían haberse prevenido con un settings.json y CLAUDE.md adecuados.
Artículos relacionados
- Guía completa de mejores prácticas de seguridad en Claude Code
- 7 casos de fallo de seguridad en Claude Code
- Guía completa de permisos en Claude Code
Referencias
Lleva tu flujo con Claude Code al siguiente nivel
50 plantillas de prompts probadas en producción, listas para copiar y pegar en Claude Code.
PDF gratuito: Hoja de trucos de Claude Code en 5 minutos
Solo deja tu correo y te enviaremos al instante la hoja de trucos en una página A4.
Cuidamos tus datos personales y nunca enviamos spam.
Sobre el autor
Masa
Ingeniero apasionado por Claude Code. Dirige claudecode-lab.com, un medio tecnológico en 10 idiomas con más de 2.000 páginas.
Artículos relacionados
Domina los costos de la API de Claude Code: 5 técnicas para pasar de $450 a $45/mes
Números reales del precio de la API de Claude Code. Aprende cómo el prompt caching, la optimización de modelos y el procesamiento por lotes lograron una reducción del 90 %, de $450 a $45 al mes.
10 patrones de prompts peligrosos en Claude Code | Qué evitar y alternativas seguras
Descubre 10 patrones de prompts peligrosos que nunca debes darle a Claude Code. Aprende cómo las instrucciones vagas provocan pérdida de código, destrucción de BD, facturas desorbitadas y filtraciones de claves.
Guía Completa de Seguridad para Claude Code: Claves API, Permisos y Protección en Producción
Guía práctica de seguridad para usar Claude Code de forma segura. Desde la gestión de claves API hasta la configuración de permisos, automatización con Hooks y protección del entorno de producción — con ejemplos de código funcionales.