Skip to content

Commit e5d3655

Browse files
committed
Schedule cleanup of stale julia caches on long-lived branches
julia-actions/cache@v3 intentionally skips delete-old-caches on the default branch, so caches on main and v1.9 accumulate without bound. Add a daily matrix job that keeps the newest julia cache per (workflow, os) tuple and deletes the rest.
1 parent e4b75c1 commit e5d3655

1 file changed

Lines changed: 87 additions & 2 deletions

File tree

.github/workflows/cleanup-caches.yml

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Cleanup github runner caches on closed pull requests
1+
name: Cleanup github runner caches
22
on:
33
# pull_request_target is required so fork PRs can delete their own caches on close.
44
# pull_request from a fork ships a read-only GITHUB_TOKEN regardless of the
@@ -7,9 +7,17 @@ on:
77
pull_request_target:
88
types:
99
- closed
10+
# Daily cleanup of stale julia caches on long-lived branches. julia-actions/cache@v3
11+
# does not run delete-old-caches on the default branch (by design, to avoid races
12+
# between concurrent runs), so caches accumulate. This job keeps only the newest
13+
# cache per (workflow, os) tuple.
14+
schedule:
15+
- cron: "0 4 * * *"
16+
workflow_dispatch:
1017

1118
jobs:
12-
cleanup:
19+
cleanup-pr:
20+
if: github.event_name == 'pull_request_target'
1321
runs-on: ubuntu-latest
1422
permissions:
1523
actions: write
@@ -65,3 +73,80 @@ jobs:
6573
GH_TOKEN: ${{ github.token }}
6674
GH_REPO: ${{ github.repository }}
6775
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
76+
77+
cleanup-branches:
78+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
79+
runs-on: ubuntu-latest
80+
permissions:
81+
actions: write
82+
strategy:
83+
fail-fast: false
84+
matrix:
85+
ref:
86+
- refs/heads/main
87+
- refs/heads/v1.9
88+
steps:
89+
- name: Cleanup stale julia caches on ${{ matrix.ref }}
90+
run: |
91+
echo "::group::Julia cache list for $REF"
92+
93+
caches=$(gh cache list --ref "$REF" --limit 100 --json id,key,createdAt,sizeInBytes \
94+
| jq -c '[.[] | select(.key | startswith("julia-cache"))]')
95+
total=$(echo "$caches" | jq 'length')
96+
97+
echo "Found $total julia cache(s) on $REF"
98+
99+
if [ "$total" -eq 0 ]; then
100+
echo "::endgroup::"
101+
echo "::notice::No julia caches on $REF"
102+
exit 0
103+
fi
104+
105+
# Group key is the cache key with run_id=... onwards stripped.
106+
# Sort by createdAt descending so the newest cache per group comes first;
107+
# awk keeps the first occurrence of each group (newest) and prints the rest.
108+
toDelete=$(echo "$caches" \
109+
| jq -r '.[] | "\(.createdAt)|\(.id)|\(.sizeInBytes)|\(.key | split(";run_id=") | .[0])"' \
110+
| sort -r \
111+
| awk -F'|' 'seen[$4]++ { print $2 "|" $3 "|" $4 }')
112+
113+
deleteCount=$(echo "$toDelete" | grep -c '|' || true)
114+
keepCount=$((total - deleteCount))
115+
116+
echo "Will delete $deleteCount stale cache(s), keep $keepCount newest per (workflow, os)"
117+
echo "::endgroup::"
118+
119+
if [ "$deleteCount" -eq 0 ]; then
120+
echo "::notice::Nothing to delete on $REF - all caches are already the newest per (workflow, os)"
121+
exit 0
122+
fi
123+
124+
## Do not fail the workflow on a single delete error.
125+
set +e
126+
127+
echo "::group::Deleting stale caches"
128+
deleted=0
129+
failed=0
130+
freedMb=0
131+
132+
while IFS='|' read -r id size group; do
133+
[ -z "$id" ] && continue
134+
mb=$((size / 1024 / 1024))
135+
echo "Deleting cache $id (${mb}MB) - group: $group"
136+
if gh cache delete "$id"; then
137+
echo " ✓ deleted"
138+
deleted=$((deleted + 1))
139+
freedMb=$((freedMb + mb))
140+
else
141+
echo " ✗ failed"
142+
failed=$((failed + 1))
143+
fi
144+
done <<< "$toDelete"
145+
146+
echo "::endgroup::"
147+
148+
echo "::notice::Cleanup complete on $REF: $deleted deleted (${freedMb}MB freed), $failed failed"
149+
env:
150+
GH_TOKEN: ${{ github.token }}
151+
GH_REPO: ${{ github.repository }}
152+
REF: ${{ matrix.ref }}

0 commit comments

Comments
 (0)