Skip to content

Commit 37a1537

Browse files
feat(ci): reconcile previews that outlived their pull request (#1461)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a75ebda commit 37a1537

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: Preview reconcile
2+
3+
# `cleanup-preview.yml` deletes a preview when its pull request closes, and that is the fast path.
4+
# It is also the only path: it exits without doing anything when COOLIFY_API_TOKEN is unset, it never
5+
# runs for a fork, and it cannot reach Coolify if Coolify is down at that moment. Nothing afterwards
6+
# notices, so a preview that outlives its pull request keeps a cloned database and a git checkout —
7+
# roughly 4 GB — until someone goes looking. This asks the opposite question on a schedule: of the
8+
# pull requests that closed recently, does any still have a preview?
9+
#
10+
# Deleting a preview for a closed pull request is idempotent, so a preview that was already cleaned
11+
# up answers 404 and costs one request. Open pull requests are never touched.
12+
13+
on:
14+
schedule:
15+
- cron: "0 4 * * *"
16+
workflow_dispatch:
17+
inputs:
18+
days:
19+
description: "How far back to look for closed pull requests"
20+
type: number
21+
default: 45
22+
23+
permissions:
24+
contents: read
25+
pull-requests: read
26+
27+
concurrency:
28+
group: preview-reconcile
29+
cancel-in-progress: false
30+
31+
jobs:
32+
reconcile:
33+
name: "Preview / Reconcile orphans"
34+
runs-on: ubuntu-latest
35+
if: vars.COOLIFY_URL != '' && vars.COOLIFY_APP_UUID != ''
36+
timeout-minutes: 10
37+
env:
38+
COOLIFY_URL: ${{ vars.COOLIFY_URL }}
39+
COOLIFY_APP_UUID: ${{ vars.COOLIFY_APP_UUID }}
40+
COOLIFY_TOKEN: ${{ secrets.COOLIFY_API_TOKEN }}
41+
GH_TOKEN: ${{ github.token }}
42+
DAYS: ${{ inputs.days || 45 }}
43+
steps:
44+
- name: Reclaim previews whose pull request has closed
45+
run: |
46+
set -euo pipefail
47+
48+
if [ -z "${COOLIFY_TOKEN}" ]; then
49+
echo "::notice::COOLIFY_API_TOKEN is not configured; nothing to reconcile against."
50+
exit 0
51+
fi
52+
53+
cutoff=$(date -u -d "-${DAYS} days" +%s)
54+
closed=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state closed \
55+
--limit 200 --json number,closedAt \
56+
--jq "[.[] | select(.closedAt != null)] | sort_by(.number) | reverse | .[] | \"\(.number) \(.closedAt)\"")
57+
58+
reclaimed=0
59+
checked=0
60+
failed=0
61+
{
62+
echo "| PR | Result |"
63+
echo "|---:|---|"
64+
} > /tmp/summary.md
65+
66+
while read -r number closed_at; do
67+
[ -n "${number:-}" ] || continue
68+
[ "$(date -u -d "${closed_at}" +%s)" -ge "${cutoff}" ] || continue
69+
checked=$((checked + 1))
70+
71+
body=$(mktemp)
72+
status=$(curl -sS -o "${body}" -w '%{http_code}' -X DELETE \
73+
-H "Authorization: Bearer ${COOLIFY_TOKEN}" \
74+
-H 'Accept: application/json' \
75+
--retry 3 --retry-connrefused --max-time 60 \
76+
"${COOLIFY_URL}/api/v1/applications/${COOLIFY_APP_UUID}/previews/${number}") || status=000
77+
78+
case "${status}" in
79+
404)
80+
: # already clean, which is the expected answer for almost every pull request
81+
;;
82+
2*)
83+
reclaimed=$((reclaimed + 1))
84+
echo "| #${number} | reclaimed — preview outlived its closed pull request |" >> /tmp/summary.md
85+
echo "::warning::Reclaimed an orphaned preview for closed PR #${number}. Its cleanup on close did not run or did not reach Coolify."
86+
;;
87+
*)
88+
failed=$((failed + 1))
89+
echo "| #${number} | FAILED — HTTP ${status} |" >> /tmp/summary.md
90+
echo "::error::Coolify returned HTTP ${status} for PR #${number}: $(head -c 200 "${body}")"
91+
;;
92+
esac
93+
done <<< "${closed}"
94+
95+
{
96+
echo "## Preview reconcile"
97+
echo
98+
echo "Checked **${checked}** pull requests closed in the last ${DAYS} days."
99+
echo
100+
if [ "${reclaimed}" -eq 0 ] && [ "${failed}" -eq 0 ]; then
101+
echo "No orphaned previews. Cleanup on close is keeping up."
102+
else
103+
cat /tmp/summary.md
104+
fi
105+
} >> "${GITHUB_STEP_SUMMARY}"
106+
107+
echo "checked=${checked} reclaimed=${reclaimed} failed=${failed}"
108+
[ "${failed}" -eq 0 ]

0 commit comments

Comments
 (0)