From 2eec4fb5d7c86322dac0fcf9c14d44b3d9600f63 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-cto[bot]" <269724374+privilegedescalation-cto[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:54:42 +0000 Subject: [PATCH 1/4] Add stale release branch cleanup workflow --- .github/workflows/stale-release-cleanup.yaml | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/stale-release-cleanup.yaml diff --git a/.github/workflows/stale-release-cleanup.yaml b/.github/workflows/stale-release-cleanup.yaml new file mode 100644 index 0000000..73b984d --- /dev/null +++ b/.github/workflows/stale-release-cleanup.yaml @@ -0,0 +1,63 @@ +name: Stale Release Branch Cleanup + +on: + schedule: + - cron: '0 9 * * 1' # Weekly every Monday at 09:00 UTC + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (no changes made)' + required: false + default: 'false' + +jobs: + cleanup-stale-branches: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github + sparse-checkout-cone-mode: false + + - name: Fetch all branches + run: git fetch --all --prune + + - name: Find and clean stale release branches + id: stale + env: + DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} + run: | + DAYS=14 + + # Find release branches older than 14 days not on main + for branch in $(git for-each-ref --format '%(refname:short)' 'refs/heads/release/*' 'refs/heads/v*'); do + ts=$(git log -1 --format='%ct' "$branch") + if [ -z "$ts" ]; then + continue + fi + age_days=$(( ($(date +%s) - ts) / 86400 )) + + if [ "$age_days" -gt "$DAYS" ]; then + # Check if branch has been merged into main + if git log --merges --ancestry-path "$branch..main" --oneline 2>/dev/null | grep -q .; then + echo "Merged branch found: $branch (age: ${age_days}d)" + if [ "$DRY_RUN" != "true" ]; then + echo "Deleting merged branch: $branch" + git push origin --delete "$branch" + else + echo "Would delete merged branch: $branch" + fi + fi + fi + done + + - name: Report dry run results + if: github.event.inputs.dry_run == 'true' + run: | + echo "Dry run complete. No branches were deleted." + echo "" + echo "Release branches found:" + git for-each-ref --format '%(refname:short) - %(committerdate:relative)' \ + 'refs/heads/release/*' 'refs/heads/v*' 2>/dev/null || echo "None found" \ No newline at end of file From f4ce7910dcd2a385851c17f021cef0de6b4a3d2d Mon Sep 17 00:00:00 2001 From: Hugh Hackman Date: Wed, 22 Apr 2026 14:26:57 +0000 Subject: [PATCH 2/4] fix: correct merge detection and branch pattern in stale-release-cleanup - Use git merge-base --is-ancestor instead of git log --merges --ancestry-path for reliable merge detection (works with squash merges and rebases) - Narrow v* glob to v[0-9]* to avoid matching vendor/ or similar Co-Authored-By: Claude Opus 4.7 --- .github/workflows/stale-release-cleanup.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale-release-cleanup.yaml b/.github/workflows/stale-release-cleanup.yaml index 73b984d..05609de 100644 --- a/.github/workflows/stale-release-cleanup.yaml +++ b/.github/workflows/stale-release-cleanup.yaml @@ -32,7 +32,7 @@ jobs: DAYS=14 # Find release branches older than 14 days not on main - for branch in $(git for-each-ref --format '%(refname:short)' 'refs/heads/release/*' 'refs/heads/v*'); do + for branch in $(git for-each-ref --format '%(refname:short)' 'refs/heads/release/*' 'refs/heads/v[0-9]*'); do ts=$(git log -1 --format='%ct' "$branch") if [ -z "$ts" ]; then continue @@ -41,7 +41,7 @@ jobs: if [ "$age_days" -gt "$DAYS" ]; then # Check if branch has been merged into main - if git log --merges --ancestry-path "$branch..main" --oneline 2>/dev/null | grep -q .; then + if git merge-base --is-ancestor "$branch" main 2>/dev/null; then echo "Merged branch found: $branch (age: ${age_days}d)" if [ "$DRY_RUN" != "true" ]; then echo "Deleting merged branch: $branch" @@ -60,4 +60,4 @@ jobs: echo "" echo "Release branches found:" git for-each-ref --format '%(refname:short) - %(committerdate:relative)' \ - 'refs/heads/release/*' 'refs/heads/v*' 2>/dev/null || echo "None found" \ No newline at end of file + 'refs/heads/release/*' 'refs/heads/v[0-9]*' 2>/dev/null || echo "None found" \ No newline at end of file From 863aba8877177786af5a88ffeb938215600e2fd4 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-ceo[bot]" <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:34:40 +0000 Subject: [PATCH 3/4] fix: address remaining QA findings in stale-release-cleanup - Add ::warning:: annotation for git push --delete failures - Change dry_run input to type: boolean for proper validation - Handle null dry_run in scheduled runs (default to false) Co-Authored-By: Claude Opus 4.7 --- .github/workflows/stale-release-cleanup.yaml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stale-release-cleanup.yaml b/.github/workflows/stale-release-cleanup.yaml index 05609de..2d43cf6 100644 --- a/.github/workflows/stale-release-cleanup.yaml +++ b/.github/workflows/stale-release-cleanup.yaml @@ -8,7 +8,8 @@ on: dry_run: description: 'Dry run (no changes made)' required: false - default: 'false' + default: false + type: boolean jobs: cleanup-stale-branches: @@ -27,7 +28,7 @@ jobs: - name: Find and clean stale release branches id: stale env: - DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} + DRY_RUN: ${{ github.event.inputs.dry_run || false }} run: | DAYS=14 @@ -43,11 +44,13 @@ jobs: # Check if branch has been merged into main if git merge-base --is-ancestor "$branch" main 2>/dev/null; then echo "Merged branch found: $branch (age: ${age_days}d)" - if [ "$DRY_RUN" != "true" ]; then - echo "Deleting merged branch: $branch" - git push origin --delete "$branch" - else + if [ "$DRY_RUN" == "true" ]; then echo "Would delete merged branch: $branch" + else + echo "Deleting merged branch: $branch" + if ! git push origin --delete "$branch" 2>&1; then + echo "::warning::Failed to delete branch: $branch" + fi fi fi fi From 4d8543040ee382700d0d79a873eccfd20f71ae13 Mon Sep 17 00:00:00 2001 From: Hugh Hackman Date: Wed, 22 Apr 2026 18:15:30 +0000 Subject: [PATCH 4/4] fix: use refs/remotes/origin for branch scanning in stale-release-cleanup In GitHub Actions, local branches don't exist - only remote branches under refs/remotes/origin/. This fixes the branch scanning loop to scan remote branches instead of local refs/heads. Also fixes the merge-base check to use the full remote ref path. --- .github/workflows/stale-release-cleanup.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/stale-release-cleanup.yaml b/.github/workflows/stale-release-cleanup.yaml index 2d43cf6..19f5481 100644 --- a/.github/workflows/stale-release-cleanup.yaml +++ b/.github/workflows/stale-release-cleanup.yaml @@ -33,8 +33,8 @@ jobs: DAYS=14 # Find release branches older than 14 days not on main - for branch in $(git for-each-ref --format '%(refname:short)' 'refs/heads/release/*' 'refs/heads/v[0-9]*'); do - ts=$(git log -1 --format='%ct' "$branch") + for branch in $(git for-each-ref --format '%(refname:strip=3)' 'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*'); do + ts=$(git log -1 --format='%ct' "refs/remotes/origin/$branch") if [ -z "$ts" ]; then continue fi @@ -42,7 +42,7 @@ jobs: if [ "$age_days" -gt "$DAYS" ]; then # Check if branch has been merged into main - if git merge-base --is-ancestor "$branch" main 2>/dev/null; then + if git merge-base --is-ancestor "refs/remotes/origin/$branch" main 2>/dev/null; then echo "Merged branch found: $branch (age: ${age_days}d)" if [ "$DRY_RUN" == "true" ]; then echo "Would delete merged branch: $branch" @@ -62,5 +62,5 @@ jobs: echo "Dry run complete. No branches were deleted." echo "" echo "Release branches found:" - git for-each-ref --format '%(refname:short) - %(committerdate:relative)' \ - 'refs/heads/release/*' 'refs/heads/v[0-9]*' 2>/dev/null || echo "None found" \ No newline at end of file + git for-each-ref --format '%(refname:strip=3) - %(committerdate:relative)' \ + 'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*' 2>/dev/null || echo "None found" \ No newline at end of file