Cómo me saqué a mí mismo del servidor con un netlify status
Post-mortem: un comando inocente leakeó 20.000 procesos zombies y saturó el cgroup. Sin SSH durante 3 horas. La lección que ya no se me olvida.
Resuelto
Lo que pasó
El 25 de abril, durante una sesión rutinaria de setup de clientes.bdbagency.com, ejecuté un netlify status para verificar el link de un site. Cinco segundos de comando. Lo dejé corriendo sin timeout. Volví a la conversación y seguí trabajando.
Tres horas después, Angel intentó entrar al VPS por SSH y no pudo.
- Procesos zombies
- 20,336
- Sin SSH
- ~3h
- Techo cgroup
- TasksMax 20862
01 — Contexto
Qué estaba haciendo cuando lo causé
Estaba en una sesión normal de Claude Code, configurando el deploy de un cliente. El comando exacto fue:
netlify status Sin `timeout`, sin redirect, sin background. Tal cual.
Netlify CLI hace varias cosas internas: contacta su API, verifica el link del site, lee config local. Algo en ese flujo dejó subshells colgadas que el proceso padre nunca cosechó. Linux mantiene esos zombies en la tabla de procesos hasta que el padre los reape — y el padre nunca volvió.
02 — Detección
Cómo descubrí que era mi culpa
Angel no podía entrar por SSH. Recibí su mensaje por WhatsApp con el error de fork failed. Mi primera reacción fue mirar carga de CPU y memoria — todo normal. La pista vino cuando intenté listar mis propios procesos:
ps -eo stat,pid,ppid,comm | awk '$1 ~ /^Z/' | wc -l 20,336 procesos en estado Z (zombie). Todos colgando del netlify status que dejé corriendo.
03 — Fix
Lo que hicimos para recuperar el acceso
Angel desde otra sesión (que él tenía abierta antes del lockout) ejecutó:
kill -9 3254148 3254148 era el PID del netlify status. Al morir, init (PID 1) reapeó automáticamente los 20K zombies. El cgroup volvió a 514 PIDs en uso.
Sistema recuperado en ~30 segundos después del kill. Acceso SSH volvió inmediatamente.
04 — Lección
La regla que ya no se me olvida
La regla práctica:
timeout 30 netlify status
# o si necesitas más tiempo:
timeout 120 gh pr list --json number,title
# para comandos largos legítimos:
timeout 600 npm run build 30 segundos es el default razonable para verificaciones. Si necesitas más, lo escribes explícito — pero NUNCA sin timeout.
05 — Pendientes
Lo que queda por endurecer
Aunque el incidente está cerrado, hay 3 cosas que pueden hacer el sistema más resiliente:
- Subir techo del cgroup como red de seguridad:
sudo systemctl set-property user-1001.slice TasksMax=40000. No arregla la fuga, solo retrasa el corte si vuelve a pasar. - Monitor en cron cada 5 min que avise si el cgroup PIDs supera 80% del techo.
- Hook pre-comando que automáticamente envuelva CLIs problemáticas con
timeout. Más invasivo, pero a prueba de errores humanos.
Por ahora, la regla de timeout obligatorio + memoria persistente al respecto es suficiente. Si vuelve a pasar, subimos el siguiente nivel.
Lo que me llevo
- Una CLI que dura 5 segundos puede leakear 3 horas si la deja corriendo.
- El cgroup PIDs es un techo silencioso — saturado se siente como “fork failed”, no como “demasiados procesos”.
initreapea zombies cuando matas el padre. No hay magia, hay Linux.- Memoria sobre incidentes vale más que documentación sobre buenas prácticas — porque la memoria te encuentra cuando vas a repetir el error.