GitHub Actions Runners 3x durch /tmp-disk-leak gestoppt (P1)

Datum

14.05.2026

Dauer

1 h 30 min

Severity
P1 · Hoch
Status

Behoben

Betroffene Services

  • GitHub Actions CI-Pipeline
  • Hetzner Self-Hosted Runners

Summary

Vitest-`mkdtemp`-Aufrufe plus eine race-safe `pnpm/action-setup`-Config haben in sechs Stunden CI-Load über 80.000 verwaiste Temp-Directories in /tmp produziert. Die einzige 437-GB-Disk auf der Hetzner-Runner-Box lief 100 % voll, alle vier Runner haben hard-crasht. Drei Recovery-Zyklen über den Tag verteilt summieren sich auf rund 90 Minuten gestörter CI-Pipeline.

Root Cause

Vitest legt für jeden Test-Run via `mkdtemp` ein temporäres Working-Directory unter `/tmp/vitest-*` an, räumt es bei einem harten Failure aber nicht zuverlässig auf. Gleichzeitig führt der race-safe `pnpm/action-setup`-Step ein Per-Job-Pnpm-Store unter `/tmp/pnpm-hetzner-<runner>-<job>` ein — ohne Cleanup-Handler im Job-Finalizer. Die Hetzner-Runner laufen auf einer Single-Disk-Box ohne separates `/tmp`-Mount, weshalb die Root-Disk dieselbe Kapazität wie die Test-Sandbox teilt. Über sechs Stunden hoher PR-Aktivität sind so 80.000+ Directories aufgelaufen, die nominell wenige KB belegen, aber durch Inode-Pressure und Block-Allocation den freien Speicher auf 0 trieben. Sobald `df` 100 % meldete, hat systemd-journald die Runner-Services in Failure-State versetzt und keine neuen Jobs mehr angenommen.

Resolution

Die unmittelbare Recovery lief drei Mal manuell: SSH auf den Runner-Host, `find /tmp -mindepth 1 -mmin +30 -delete` plus `df`-Verify, anschließend `systemctl restart actions.runner.*`. Der nachhaltige Fix landete als PR #658 — ein Hourly-Cron-Job auf dem Host (`/etc/cron.hourly/cleanup-tmp`) führt denselben `find`-Befehl jede Stunde aus und schreibt die Anzahl gelöschter Pfade in `/var/log/cleanup-tmp.log`. Zusätzlich haben wir ein Disk-Watchdog-Threshold von 80 % auf dem Host eingerichtet, das einen Slack-Alert feuert, bevor der nächste Crash passieren kann. Die Vitest- und pnpm-Setup-Konfigs bleiben unverändert; die Verantwortung für Cleanup liegt jetzt explizit beim Host-Cron, nicht beim Test-Framework.

Preventive Actions

  • Hourly Cleanup-Cron permanent auf allen Hetzner-Runnern installiert (PR #658).

  • Disk-Monitoring auf 80-%-Threshold mit Slack-Alert ausgerollt, damit wir 4-6 Stunden Vorlauf vor einem Full-Disk-Event haben.

  • Künftig: vitest-Config mit explizitem `tmpdir` plus After-All-Cleanup-Hook, damit auch ohne Host-Cron nichts leakt.

  • Runbook-Eintrag für `/tmp-disk-full`-Diagnose plus Copy-paste-Recovery-Befehle in `docs/deployment/runbook.md` ergänzt.

Timeline

Zeit (UTC)Ereignis
04:00Erster Disk-Full-Crash: alle vier Hetzner-Runner gehen offline.
04:15Manual Mass-Cleanup #1 (~28.000 verwaiste /tmp-Dirs entfernt).
14:00Zweiter Crash desselben Musters; CI-Queue staut sich an.
14:20Manual Mass-Cleanup #2 (~31.000 Dirs).
16:00Dritter Crash; Root-Cause auf vitest + pnpm-setup-Leak geklärt.
16:25Manual Mass-Cleanup #3 (~24.000 Dirs).
22:00Hourly-Cleanup-Cron via PR #658 deployed und live-getestet.