From ee6999376027ecfc34162a99fbfc98482aa6e544 Mon Sep 17 00:00:00 2001 From: Fedor Borshev Date: Wed, 26 Aug 2026 09:46:57 +0200 Subject: [PATCH] Cancel superseded CI runs and skip image builds PRs cannot affect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modern agent-driven development ("vibe-coding") produces many PRs, and CI minutes scale with them: in zoya, a production project running this template's CI shape, GitHub Actions alone began costing ~$100/month. The two biggest line items were superseded runs of the same PR completing pointlessly, and every PR building all three Docker images even when nothing image-affecting changed. Port both cost guards from zoya: - A concurrency group per workflow and ref, in both the template repo's own CI and the CI generated projects inherit, cancelling in-progress runs only for pull requests — a push to master is never cancelled. - In the generated project's workflow, the lint job asks the GitHub API for the PR's changed files and reports whether any of them can affect the image build (Dockerfile, uv.lock, pyproject.toml, .dockerignore). build-docker-image now also needs lint (execution order is unchanged, test already needs it) and skips on PRs that touch none of those files; pushes to master always build. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 4 +++ .../.github/workflows/ci.yml | 28 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 676672c1..7dbca144 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: pull_request: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: bootstrap: runs-on: ubuntu-latest diff --git a/{{ cookiecutter.name }}/.github/workflows/ci.yml b/{{ cookiecutter.name }}/.github/workflows/ci.yml index e93e12a9..ba04a51a 100644 --- a/{{ cookiecutter.name }}/.github/workflows/ci.yml +++ b/{{ cookiecutter.name }}/.github/workflows/ci.yml @@ -7,14 +7,39 @@ on: - main pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + image-build-affected: ${{ steps.image-build-affected.outputs.result }} steps: - name: checkout uses: actions/checkout@v4 + - name: Check whether the pull request can affect the image build + id: image-build-affected + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + changed=$(gh api --paginate "/repos/$REPO/pulls/$PR_NUMBER/files" --jq '.[].filename') + if grep -qxE 'Dockerfile|uv\.lock|pyproject\.toml|\.dockerignore' <<< "$changed"; then + echo "result=true" >> "$GITHUB_OUTPUT" + else + echo "result=false" >> "$GITHUB_OUTPUT" + fi + - name: setup project id: setup-project uses: ./.github/actions/setup-project @@ -67,7 +92,8 @@ jobs: run: make test build-docker-image: - needs: test + needs: [lint, test] + if: github.event_name != 'pull_request' || needs.lint.outputs.image-build-affected == 'true' runs-on: ubuntu-latest steps: