From 2c6d6a02cdfdcf610a89724eb8f52cffaeef45dc Mon Sep 17 00:00:00 2001 From: CaliBrain Date: Mon, 21 Sep 2026 02:38:06 -0400 Subject: [PATCH] ci: debounce dev image builds instead of building nightly (#1376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the nightly cron and its check-changes job with a debounce. Every push to main starts dev-image-debounce.yml, which waits out the 60-minute wait timer on the dev-image-debounce environment, then dispatches the Docker workflow only if main still points at its commit. A burst of merges now publishes one dev image, an hour after the last merge. The Docker workflow keeps only its tag and workflow_dispatch triggers, so its history holds real builds only. The debounce workflow deletes its own finished runs, so no-op runs don't pile up either. Requires the dev-image-debounce environment with a 60-minute wait timer (Settings → Environments). --- .../build-and-publish-docker-image.yml | 40 +-------------- .github/workflows/dev-image-debounce.yml | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/dev-image-debounce.yml diff --git a/.github/workflows/build-and-publish-docker-image.yml b/.github/workflows/build-and-publish-docker-image.yml index 5f9b256..3d82d8c 100644 --- a/.github/workflows/build-and-publish-docker-image.yml +++ b/.github/workflows/build-and-publish-docker-image.yml @@ -3,10 +3,8 @@ on: push: tags: - 'v*' - schedule: - # Nightly at 03:17 UTC — only builds if there are new commits on main - # since the last successful run (see check-changes job). - - cron: '17 3 * * *' + # Also dispatched on main by dev-image-debounce.yml, once main has been + # quiet for an hour, to publish the dev image. workflow_dispatch: permissions: read-all @@ -14,41 +12,7 @@ env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository_owner }}/shelfmark jobs: - check-changes: - runs-on: ubuntu-latest - outputs: - should_build: ${{ steps.check.outputs.should_build }} - steps: - - name: Check for new commits since last successful build - id: check - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - EVENT_NAME: ${{ github.event_name }} - CURRENT_SHA: ${{ github.sha }} - REPO: ${{ github.repository }} - run: | - # Always build on tag pushes and manual dispatch. - if [[ "$EVENT_NAME" != "schedule" ]]; then - echo "Event is $EVENT_NAME — building unconditionally." - echo "should_build=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Scheduled run: only build if HEAD differs from the last successful build on main. - LAST_SHA=$(gh api "/repos/${REPO}/actions/workflows/build-and-publish-docker-image.yml/runs?branch=main&status=success&per_page=1" --jq '.workflow_runs[0].head_sha' 2>/dev/null || true) - echo "Last successful build SHA: ${LAST_SHA:-}" - echo "Current HEAD SHA: ${CURRENT_SHA}" - if [[ -z "$LAST_SHA" || "$LAST_SHA" != "$CURRENT_SHA" ]]; then - echo "New commits detected — building." - echo "should_build=true" >> "$GITHUB_OUTPUT" - else - echo "No new commits since last successful build — skipping." - echo "should_build=false" >> "$GITHUB_OUTPUT" - fi - build-and-push-images: - needs: check-changes - if: needs.check-changes.outputs.should_build == 'true' runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/dev-image-debounce.yml b/.github/workflows/dev-image-debounce.yml new file mode 100644 index 0000000..ea6d0fc --- /dev/null +++ b/.github/workflows/dev-image-debounce.yml @@ -0,0 +1,51 @@ +name: Debounce dev image +# A burst of merges to main should publish one dev image, not one per commit. +# Each push to main waits out the 60-minute wait timer on the +# dev-image-debounce environment (Settings → Environments; waiting holds no +# runner), then dispatches the Docker workflow only if main still points at +# its commit. So only the last push of a burst builds, and the Docker +# workflow's history holds real builds only. +on: + push: + branches: + - main +permissions: {} + +jobs: + debounce: + runs-on: ubuntu-latest + environment: + name: dev-image-debounce + deployment: false + permissions: + actions: write # dispatch the build, delete finished debounce runs + contents: read + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + steps: + - name: Dispatch the dev build if main is still at this commit + env: + CURRENT_SHA: ${{ github.sha }} + run: | + HEAD_SHA=$(gh api "repos/${GH_REPO}/git/ref/heads/main" --jq '.object.sha') + echo "This run's commit: ${CURRENT_SHA}" + echo "main HEAD now: ${HEAD_SHA}" + if [[ "$HEAD_SHA" == "$CURRENT_SHA" ]]; then + echo "No newer commits on main — dispatching the dev build." + gh workflow run build-and-publish-docker-image.yml --ref main + else + echo "main has moved on — the newer push's run will build it." + fi + + # A finished debounce run is noise: it either did nothing or its build + # run is the record. Best effort, since runs from the same burst race to + # delete the same runs. + - name: Delete finished debounce runs + run: | + # On an HTTP error gh prints the error body to stdout, so bail out + # rather than loop over it. + ids=$(gh api "repos/${GH_REPO}/actions/workflows/dev-image-debounce.yml/runs?status=success&per_page=100" --jq '.workflow_runs[].id') || exit 0 + for id in $ids; do + gh api -X DELETE "repos/${GH_REPO}/actions/runs/${id}" || true + done