Skip to content

Cleanup

Cleanup #803

Workflow file for this run

name: Cleanup
on:
pull_request_target:
types: [closed]
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
permissions:
contents: read
actions: write
pull-requests: read
jobs:
# --- PR close: worker/container cleanup ---
# Uses pull_request_target so it fires for fork PRs too (needs secrets).
# Safe: only deletes resources keyed by PR number, never executes PR code.
#
# Image tags (pr-* and ci-*) are NOT deleted here. Deleting pr-* tags was
# observed to break the ci-* content-addressed Docker cache, likely because
# they share underlying manifests and deletion triggers GC. Image cleanup
# happens in the daily sweep only for closed/stale PRs.
cleanup-pr:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Install wrangler
run: npm install -g wrangler@latest
- name: Delete GHA caches
timeout-minutes: 1
continue-on-error: true
run: gh cache delete --all --ref "refs/pull/${{ github.event.pull_request.number }}/merge" || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Delete test workers and containers
timeout-minutes: 3
continue-on-error: true
run: |
PR=${{ github.event.pull_request.number }}
CONTAINERS=$(wrangler containers list --json 2>/dev/null || echo "[]")
WORKER="sandbox-e2e-test-worker-pr-${PR}"
IDS=$(echo "$CONTAINERS" | jq -r ".[] | select(.name == \"$WORKER\" or (.name | startswith(\"$WORKER-\"))) | .id" 2>/dev/null)
wrangler delete --name "$WORKER" 2>/dev/null || true
for id in $IDS; do
wrangler containers delete "$id" 2>/dev/null || true
done
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# --- Daily sweep: backstop for stale resources + image cache eviction ---
cleanup-stale:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: |
docker-images.txt
.github/load-docker-images.sh
sparse-checkout-cone-mode: false
persist-credentials: false
fetch-depth: 1
- name: Install wrangler
run: npm install -g wrangler@latest
- name: Clean stale PR resources
timeout-minutes: 10
run: |
# Discover candidate PRs from BOTH containers and images so we
# catch orphaned resources even when one source is already cleaned.
CONTAINERS=$(wrangler containers list --json 2>/dev/null || echo "[]")
CONTAINER_PRS=$(echo "$CONTAINERS" | jq -r '.[].name' 2>/dev/null \
| grep -oE 'pr-[0-9]+' | sed 's/pr-//' | sort -u || true)
ALL_IMAGES=$(wrangler containers images list --json 2>/dev/null || echo "[]")
IMAGE_PRS=$(echo "$ALL_IMAGES" | jq -r '.[].tags[]?' 2>/dev/null \
| grep -oE '^pr-[0-9]+$' | sed 's/pr-//' | sort -u || true)
PR_NUMBERS=$(printf '%s\n' $CONTAINER_PRS $IMAGE_PRS | sort -un)
if [ -z "$PR_NUMBERS" ]; then
echo "No PR resources found"
exit 0
fi
STALE_THRESHOLD=$((7 * 24 * 60 * 60))
NOW=$(date +%s)
declare -A SHOULD_CLEAN
for PR in $PR_NUMBERS; do
PR_INFO=$(gh pr view "$PR" --json state,updatedAt 2>/dev/null || echo "")
if [ -z "$PR_INFO" ]; then
SHOULD_CLEAN[$PR]=true
continue
fi
STATE=$(echo "$PR_INFO" | jq -r '.state')
if [ "$STATE" = "CLOSED" ] || [ "$STATE" = "MERGED" ]; then
SHOULD_CLEAN[$PR]=true
elif [ "$STATE" = "OPEN" ]; then
UPDATED=$(echo "$PR_INFO" | jq -r '.updatedAt')
UPDATED_EPOCH=$(date -d "$UPDATED" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$UPDATED" +%s 2>/dev/null)
if [ $((NOW - UPDATED_EPOCH)) -gt $STALE_THRESHOLD ]; then
SHOULD_CLEAN[$PR]=true
else
SHOULD_CLEAN[$PR]=false
fi
fi
done
source .github/load-docker-images.sh
for PR in $PR_NUMBERS; do
if [ "${SHOULD_CLEAN[$PR]}" = "true" ]; then
echo "PR #$PR is stale — cleaning up"
WORKER="sandbox-e2e-test-worker-pr-${PR}"
IDS=$(echo "$CONTAINERS" | jq -r ".[] | select(.name == \"$WORKER\" or (.name | startswith(\"$WORKER-\"))) | .id" 2>/dev/null)
wrangler delete --name "$WORKER" 2>/dev/null || true
for id in $IDS; do
wrangler containers delete "$id" 2>/dev/null || true
done
# Delete pr-* images for closed/stale PRs. These tags are no
# longer referenced by any active CI run. Note: if the CF
# registry GC's shared manifests on tag deletion, this could
# invalidate ci-* tags sharing the same digest — the cache
# rebuilds on next use (~6 min one-time cost).
for image in "${DOCKER_IMAGES[@]}"; do
wrangler containers images delete "$image:pr-$PR" 2>/dev/null || true
done
fi
done
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Clean stale GHA caches
timeout-minutes: 2
run: |
CACHES=$(gh cache list --json ref -q '.[].ref' 2>/dev/null || echo "")
echo "$CACHES" | grep -E 'refs/pull/[0-9]+/merge' | while read -r REF; do
PR=$(echo "$REF" | grep -oE '[0-9]+')
STATE=$(gh pr view "$PR" --json state -q '.state' 2>/dev/null || echo "NOT_FOUND")
if [ "$STATE" = "CLOSED" ] || [ "$STATE" = "MERGED" ] || [ "$STATE" = "NOT_FOUND" ]; then
gh cache delete --all --ref "$REF" || true
fi
done || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}