惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
人人都是产品经理
人人都是产品经理
博客园_首页
爱范儿
爱范儿
博客园 - 叶小钗
aimingoo的专栏
aimingoo的专栏
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 【当耐特】
Y
Y Combinator Blog
量子位
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
The Blog of Author Tim Ferriss
月光博客
月光博客
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
A
About on SuperTechFans

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
My CI Runner Was Killed by My Own Script: The Dark Side o...
Mustafa ERBA · 2026-05-09 · via DEV Community

Mustafa ERBAY

Towards the end of last month, I started a build job on my self-hosted GitHub Actions runner. It was a job that normally took 10-15 minutes, but this time it just wouldn't finish. The job seemed stuck, and I wasn't getting any response from the runner. When I tried to connect to the server via SSH, the connection was refused. It felt similar to the OOM scenarios I'd experienced on my VPS where sshd couldn't accept connections, but this time my RAM usage was normal.

After some digging, I realized my runner's heart had stopped beating. Looking at the GitHub Actions panel, I saw the runner was "Offline". The interesting thing was that the server itself was up, and my other Docker containers were running without issues. The only problem was my CI runner.

Getting to the Root of the Problem: A Cleanup Script Murder

To understand why the runner had died, I connected to the server via console. My first task was to check the dmesg output. There was nothing surprising there; no kernel-level error or OOM killer trigger was visible. When I checked the service status with the systemctl status github-runner command, I encountered an even more interesting situation: the service was active (exited), and there were no error messages in the logs. It was as if someone had gracefully shut down the service.

It was at this exact moment that the "innocent" cleanup script I'd added last week came to mind. I manage over 13 Docker containers on my own VPS, and disk space can sometimes become critical. Especially Docker's build cache and unused images, with 33 GB of build cache and 23 GB of unused images, can fill my disk up to 100%. Because of this, I had written a script to clean up old build outputs and unnecessary files in the _work directory.

⚠️ Chaos of My Own Making

This kind of automation can be a lifesaver, yes. But if it's not tested sufficiently or if scenarios aren't well thought out, shooting yourself in the foot becomes inevitable. My scenario was exactly that.

The Killer Script and the Victim Runner

The script I wrote simply deleted files older than a certain age in the _work directory. However, I had overlooked a small detail: the runner itself also operated within the _work directory, and temporary directories like _temp, or even in some cases the runner's own binaries or configuration files, could fall within this scope. I had previously experienced the pain of deleting directories inside _work/_temp on a GitHub Actions runner, but this time I had gone even further.

I hadn't used parameters like maxdepth or prune in the find command within the script carefully enough. While my goal was only build artifacts, the script had deleted some files vital for the runner itself. The result: The runner service quietly shut down when it couldn't access the necessary files to continue operating. This was a resource management disaster, similar to my Astro build consuming 2.5 GB of RAM and hitting OOM, but this time it was disk and file system related.

# A snippet from the faulty cleanup script (simplified version)
# This command was deleting all files older than 7 days under the _work directory.
# However, the runner's own working files were also included in this scope.
find /home/runner/_work/ -type f -mtime +7 -delete
find /home/runner/_work/ -type d -empty -delete

Enter fullscreen mode Exit fullscreen mode

This command, `/home