Tips & Tricks

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.json tenía Bash(git add*) en la lista allow, 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 -rf estaba en allow en vez de ask

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 --force en 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 update actualizó 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

Referencias

#claude-code #incident #production #sre #security #postmortem

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.

Gratis

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.

Masa

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.