From b8c4515583549fa2a2044add0c960894ce70bd2f Mon Sep 17 00:00:00 2001 From: ian-flores Date: Tue, 13 Jan 2026 07:09:24 -0800 Subject: [PATCH 1/3] fix(ci): improve cleanup timing and reduce unnecessary releases - Adhoc image cleanup now waits for main build to succeed before deleting merged PR images (ensures stable fallback exists) - Non-merged PRs still cleanup immediately on close - Uses commit SHA to identify correct PR (race-condition safe) - Release workflow skips docs, markdown, and workflow-only changes --- .github/workflows/cleanup-adhoc-images.yml | 54 ++++++++++++++++++++-- .github/workflows/release.yml | 4 ++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cleanup-adhoc-images.yml b/.github/workflows/cleanup-adhoc-images.yml index 6902c120..e341cf1a 100644 --- a/.github/workflows/cleanup-adhoc-images.yml +++ b/.github/workflows/cleanup-adhoc-images.yml @@ -1,7 +1,8 @@ # Cleanup Adhoc GHCR Images # -# This workflow automatically deletes adhoc GHCR images when a PR is closed. -# Adhoc images are temporary testing images pushed during PR development. +# This workflow automatically deletes adhoc GHCR images: +# - For non-merged PRs: immediately when the PR is closed +# - For merged PRs: after the main branch build succeeds (ensuring a stable image exists) # # Tag format: adhoc-{sanitized-branch-name}-{version} @@ -10,9 +11,14 @@ name: Cleanup adhoc GHCR images on: pull_request: types: [closed] + workflow_run: + workflows: ["build/push team-operator"] + types: [completed] + branches: [main] permissions: packages: write + pull-requests: read env: GHCR_ORG: posit-dev @@ -21,21 +27,61 @@ jobs: cleanup: runs-on: ubuntu-latest name: cleanup-adhoc-images + # Run if: + # 1. PR closed without merging (cleanup immediately) + # 2. Build workflow completed successfully on main (cleanup merged PR's images) + if: | + (github.event_name == 'pull_request' && github.event.pull_request.merged == false) || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') strategy: fail-fast: false matrix: package: [team-operator, flightdeck] steps: + - name: Get branch name for cleanup + id: branch-name + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # PR was closed without merging - use the PR's head branch + BRANCH_NAME="${{ github.head_ref }}" + echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT + echo "Branch from closed PR: $BRANCH_NAME" + else + # workflow_run event - find the PR associated with the build's commit SHA + # This prevents race conditions when multiple PRs merge in quick succession + COMMIT_SHA="${{ github.event.workflow_run.head_sha }}" + echo "Looking for PR associated with commit: $COMMIT_SHA" + + # Use the commits API to find PRs associated with this specific commit + BRANCH_NAME=$(gh api \ + "/repos/${{ github.repository }}/commits/${COMMIT_SHA}/pulls" \ + --jq '.[0].head.ref' \ + 2>/dev/null || echo "") + + if [ -z "$BRANCH_NAME" ] || [ "$BRANCH_NAME" = "null" ]; then + # Fallback: might be a direct push to main (not a PR merge) + echo "No PR found for commit $COMMIT_SHA (may be a direct push to main)" + echo "branch=" >> $GITHUB_OUTPUT + else + echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT + echo "Branch from PR associated with commit $COMMIT_SHA: $BRANCH_NAME" + fi + fi + - name: Compute tag prefix from branch name id: tag-prefix + if: steps.branch-name.outputs.branch != '' run: | - BRANCH_NAME="${{ github.head_ref }}" + BRANCH_NAME="${{ steps.branch-name.outputs.branch }}" SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '/' '-') TAG_PREFIX="adhoc-${SANITIZED_BRANCH}-" echo "prefix=$TAG_PREFIX" >> $GITHUB_OUTPUT echo "Cleaning up tags with prefix: $TAG_PREFIX" - name: Delete adhoc package versions + if: steps.branch-name.outputs.branch != '' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PACKAGE: ${{ matrix.package }} @@ -75,8 +121,10 @@ jobs: echo "Deleted $DELETED adhoc version(s)" - name: Summary + if: steps.branch-name.outputs.branch != '' run: | echo "### Adhoc Image Cleanup: ${{ matrix.package }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** \`${{ steps.branch-name.outputs.branch }}\`" >> $GITHUB_STEP_SUMMARY echo "- **Tag prefix:** \`${{ steps.tag-prefix.outputs.prefix }}\`" >> $GITHUB_STEP_SUMMARY echo "- **Package:** \`ghcr.io/${{ env.GHCR_ORG }}/${{ matrix.package }}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23b3780d..58b97e62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,10 @@ on: push: branches: - main + paths-ignore: + - '*.md' + - 'docs/**' + - '.github/workflows/**' workflow_dispatch: permissions: From f9e3d3d088ab7a429c38148b8f6e9b79bb8a6f5c Mon Sep 17 00:00:00 2001 From: ian-flores Date: Tue, 13 Jan 2026 08:08:22 -0800 Subject: [PATCH 2/3] fix(ci): grant actions write permission for GHA cache The Docker buildx GHA cache (type=gha) requires actions: write permission to save cache entries. Without it, the cache API returns 502 errors when attempting to write. --- .github/workflows/build.yml | 2 +- .github/workflows/flightdeck.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71397fcd..c8966500 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ on: - 'docs/**' permissions: - actions: read + actions: write contents: read id-token: write packages: write diff --git a/.github/workflows/flightdeck.yml b/.github/workflows/flightdeck.yml index d35456a8..700f7c57 100644 --- a/.github/workflows/flightdeck.yml +++ b/.github/workflows/flightdeck.yml @@ -18,7 +18,7 @@ on: workflow_dispatch: permissions: - actions: read + actions: write contents: read id-token: write packages: write From 04536030791de01a02c6556f1c9fe3abdfaaeea3 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Tue, 13 Jan 2026 08:32:49 -0800 Subject: [PATCH 3/3] fix(ci): add ignore-error to Docker cache for resilience Cache operations can fail transiently. Adding ignore-error=true ensures builds complete even when cache read/write fails. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8966500..5b8ce71b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,8 +156,8 @@ jobs: build-args: | VERSION=${{ steps.metadata.outputs.version }} GO_VERSION=${{ steps.metadata.outputs.go-version }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,ignore-error=true + cache-to: type=gha,mode=max,ignore-error=true - name: Show image size run: docker image ls