Sabah uyandığımda 16 cron arka arkaya fail olmuştu
Disk-cleanup timer’ım dün gece 03:30’da çalıştı (planladığım gibi). Sabaha geldiğimde GitHub Actions paneli kıpkırmızı: 16 cron başarısız. Hiçbiri yeni içerik üretmemiş. Pipeline-health monitor “DEGRADED” mailini atmış. Inbox’ta tek mail.
Run logları açtım. Hepsi aynı yerde patlıyordu — Checkout adımı başarılı, Verify Node başarılı, Install dependencies başarılı, sonra bir sonraki adım hiç başlamadan workflow ölüyordu. Hiç olağan değildi.
GitHub’ın UI’ında detay görünmüyordu — runner-side error mesajı log’a yazılamıyor, sadece “Job failed” gösteriyordu. VPS’e ssh attım:
$ ssh vps 'sudo journalctl -u "actions.runner.*" --since "8 hours ago" | grep -iE "error|fail|missing"' | head -10
Missing file: /home/github-runner/runner-mustafaerbay/_work/_temp/_runner_file_commands/set_output_xyz123
Missing file: /home/github-runner/runner-mustafaerbay/_work/_temp/_runner_file_commands/set_output_abc456
Missing file: ...
Missing file: set_output_*. GitHub Actions runner’ın step’ler arası state için kullandığı dosyalar.
GitHub Actions step’leri birbirine state geçirmek için $GITHUB_OUTPUT adında bir dosya kullanır:
echo "new_slugs=$NEW_SLUGS" >> "$GITHUB_OUTPUT"
Bu dosya _work/_temp/_runner_file_commands/ altında. Runner her step için unique bir dosya yaratır. Yapılandırılmış formatta yazılır, sonraki step’te ${{ steps.<id>.outputs.<name> }} olarak okunur.
Eğer dosya yoksa runner kaybolmuş gibi davranır. Bu da workflow’un fail olmasına neden oluyor.
Sebebi suçluluk hissiyle buldum
Önceki gün disk-cleanup.sh script’imi yazmıştım. İçinde şu satır vardı:
find /home/github-runner -path '*/_work/_temp/*' -mtime +7 -delete
7 günden eski her şeyi silen bu satır — directory’ler de dahil. _runner_file_commands directory’si 7 gün önce yaratılmış olabilir, ama runner onu hâlâ kullanıyor.
-delete flag’i find’e dosya VE dizin silmesini söyler. Boş olmayan dizinleri silmez ama 7 gün önce yaratılmış sonra son hareketlilik 1 hafta öncesinden eski olabilir → silinmiş gibi davranabilir.
İlk işim ne olduğunu anlamaktı. SSH’tan baktım:
$ ls -la /home/github-runner/runner-mustafaerbay/_work/_temp/_runner_file_commands/
total 8
drwxr-xr-x 2 github-runner github-runner 4096 May 3 03:30 .
drwxr-xr-x 5 github-runner github-runner 4096 May 3 03:30 ..
Boş. Sadece ”.” ve ”..” var. Runner bu dizini var sayıyor, içine yazıyor, ama bir saatlik cron geldiğinde dizin var ama içindeki dosyalar yok → runner crash.
Aslında runner restart olduğunda da problem yok çünkü kendi yeniden initialize ediyor. Ama 03:30’da sleep mode’daydı. Cron tetiklendiğinde state’i bekliyordu, bulunmuyordu, fail.
Hızlı kurtarma
Runner servisini restart ettim:
$ ssh vps 'sudo systemctl restart actions.runner.<repo-slug>.<runner-name>.service'
Restart’tan sonra runner kendi state directory’sini yeniden oluşturdu. Sonraki cron başarılı geçti.
Disk-cleanup.sh’ı düzelttim. Sadece dosyalar silinecek, dizinler dokunulmayacak:
# Eski (TEHLIKELI)
find /home/github-runner -path '*/_work/_temp/*' -mtime +7 -delete
# Yeni (güvenli — sadece bilinen tek-kullanımlık dosya pattern'leri)
find /home/github-runner -path '*/_work/_temp/*' -type f \
\( -name 'set_output_*' -o -name 'set_env_*' -o -name 'add_path_*' -o -name '*.tmp' -o -name '*.log' \) \
-mtime +7 -delete
İki kritik fark:
-type f— sadece dosya, dizin değil-namewhitelist’i — sadece bilinen tek-kullanımlık dosya isimleri
Runner state directory’leri (_runner_file_commands gibi) artık dokunulmuyor. Eski tek-kullanımlık set_output_* dosyaları temizleniyor (bunlar tek bir step için yaratılır, kullanıldıktan sonra anlamı kalmaz).
Daha derin ders
Bu yazıyı yazmamın asıl sebebi kendi yarattığım bir incident’ın utancını paylaşmak değil. Sebep daha derinde:
“Otomasyon kurarken, otomasyonun girdisini anlamadan değiştirmek, otomasyonun kendisinden daha tehlikeli.”
Disk-cleanup.sh’ı yazarken _work/_temp “geçici dosyalar” diye düşündüm. Kelime temp zaten “temporary”. 7 günden eski olanlar muhtemelen “kalıntı”. Mantıklı gibi.
Aslında değil. _work/_temp runner için active state depolama yeri. İsmi temp ama runner için _runner_file_commands her step başında yaratılan, step bitince kullanılan kritik state. 7 gün boyunca persistent oluyor çünkü runner uzun süre uyuyor olabilir.
Aşağı satır: otomasyonu kurmadan önce, etkilediğin sistemin sözleşmelerini öğren.
Disk-cleanup.sh için artık şu prensip:
- Whitelist > blacklist (silinecek pattern’i listele, “her şey eski” deme)
- Dosya > dizin (dizinlerin var olmaya devam etmesi state için kritik olabilir)
- Saatleri zam et (7 gün belki kısa, runner uzun süre idle olabilir, 14 güne çıkardım)
Sonuç
Bu olay benim için bir kalibrasyon hatasıydı. Disk-cleanup.sh işine yarayan bir script’i ama yapılan kapsam hatası sahibinin kendi sistem’ini kırdı. Bunu açıkça yazıyorum çünkü 16 saatlik downtime’ın faili ben’im, GitHub değil, AI değil, third-party bug değil.
Runbook yazarken üç soru daha sormak için kendime not aldım:
- Bu script ne siliyor? (Tam olarak. Liste.)
- Sildiği şeylerin sahibi kim? (Sistem servisi mi? Runner mı? Application data mı?)
- Bu sahibin “temizlik kabul ettiği” pattern listesi var mı? (Yoksa sorma izni almıyorum demek.)
Bu üç sorudan hiçbirini sormadan yazdığım find ... -delete 16 saat outage demek. Sormak 30 saniye demek. Trade artık çok belli.