fix(ci): moving checkouts to a shared volume made them leak forever #5605
ccantynzAI Reviewcommented 23h ago
AI Triage
(no summary)
Priority: medium Risk area: mixed
Suggested labels: (no label suggestions) Suggested reviewers: (no reviewer suggestions)
Suggestions only — nothing has been applied. The PR author stays in control.
gluecron[bot]🤖 botAI Reviewcommented 23h ago
AI review unavailable
The platform's AI balance is exhausted, so AI generation is temporarily unavailable. Nothing was lost. You can queue this as a repair for the internal agent from the repository's Health page, or try again once the balance is restored. The PR is otherwise unchanged.
Cross-repo impact
See what breaks downstream if this PR merges.
⮌ Merged
This pull request was merged into main.
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts
Seven orphaned checkouts, 1.5 GB, in 75 minutes — on a 150 GB disk shared with five other tenants and already 64% full.
Checkouts used to be
mkdtemp'd inside the app container, so a leftover directory vanished with the next container restart (roughly hourly here). Moving them to the sharedci-workvolume this morning, so a sibling runner could see them, made them persistent — turning a self-clearing mess into an unbounded one. Each is a full clone plusnode_modules, ~214 MB.A full disk on this box is an outage for every tenant on it — the same failure the broker's container cleanup exists to prevent, arriving by a different route.
Diagnosis, because the obvious suspect was innocent
The in-run cleanup looked wrong:
rm(join(checkoutDir, "..")), a path ending in... I tested it rather than assuming —join()normalises it and the delete works fine. The app logs showed no cleanup errors either, though that proved nothing: the container had been recreated minutes earlier, so only minutes of logs existed.The real cause is that the cleanup line is never reached. The routine victim is a run killed by the very deploy that recreates the container, and no
finallysurvives SIGKILL. Today's cadence — seven merges in an afternoon, each deploy killing an in-flight run — accounts for all seven orphans exactly.The fix
A sweeper on the same schedule as
reapStuckRuns, removinggluecron-run-*directories older than two hours — againstreapStuckRunsmarking anything still running after one hour as abandoned, so the extra hour is pure margin. Deleting a live job's working directory would be a far worse bug than keeping a dead one an hour longer.Steady-state bound is therefore (runs per two hours) × ~214 MB, rather than unbounded.
Two guards, both mutation-verified by deleting them and watching specific tests fail:
gluecron-run-prefix). The volume is shared; removing a directory we didn't create isn't cleanup, it's data loss in someone else's service.Tests run against a real temp directory rather than a mocked fs, because what's being asserted is what happens to files.
Also corrects a comment
The in-run cleanup was labelled
// Cleanup always runs.It isn't in afinally, and SIGKILL can't be caught regardless — a claim the code didn't support, sitting directly above the line whose failure to run produced the leak.