From cf93b02415dd24217601d8b1f36282677eab2e5c Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 20:00:27 -0500 Subject: [PATCH 1/3] ci: supersede stale PR build runs and skip draft PRs Add concurrency groups to the heavy build workflows (build.yml esp32 example matrix, build_libraries.yml host C++/Python libs, and build_wheels.yml Python wheels) so that pushing a new commit to a PR cancels the earlier in-progress run. The group is keyed by workflow + PR number (globally unique, unlike head branch names which can collide across forks), falling back to github.ref. cancel-in-progress is gated to pull_request events so push-to-main and release runs are never cancelled. Add a job-level draft guard to each heavy build job so build CI is skipped for draft PRs while still running on push-to-main and non-draft PRs. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 12 ++++++++++++ .github/workflows/build_libraries.yml | 16 ++++++++++++++++ .github/workflows/build_wheels.yml | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 257545428..c4bec6e36 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,8 +10,20 @@ on: - '!**/idf_component.yml' - '!**/README.md' +# Supersede in-progress runs: a new commit on the same PR cancels the earlier, +# now-stale build. Keyed by the workflow (so it never cross-cancels other +# workflows) + the PR number, which is globally unique - unlike the head branch +# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# cancel-in-progress is gated to pull_request events only so that push-to-main +# runs are never cancelled (distinct refs also get distinct groups anyway). +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest continue-on-error: true diff --git a/.github/workflows/build_libraries.yml b/.github/workflows/build_libraries.yml index 8cc5645e5..24b241f3a 100644 --- a/.github/workflows/build_libraries.yml +++ b/.github/workflows/build_libraries.yml @@ -9,8 +9,20 @@ on: types: [published] workflow_dispatch: +# Supersede in-progress runs: a new commit on the same PR cancels the earlier, +# now-stale build. Keyed by the workflow (so it never cross-cancels other +# workflows) + the PR number, which is globally unique - unlike the head branch +# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# cancel-in-progress is gated to pull_request events only so that push-to-main +# (and release) runs are never cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build_windows: + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: windows-latest continue-on-error: false @@ -42,6 +54,8 @@ jobs: path: lib/pc build_linux: + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest continue-on-error: false @@ -70,6 +84,8 @@ jobs: path: lib/pc build_macos: + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: macos-latest continue-on-error: false diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index dce044e31..7cac77832 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -19,9 +19,21 @@ on: permissions: contents: read +# Supersede in-progress runs: a new commit on the same PR cancels the earlier, +# now-stale build. Keyed by the workflow (so it never cross-cancels other +# workflows) + the PR number, which is globally unique - unlike the head branch +# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# cancel-in-progress is gated to pull_request events only so that push-to-main +# (and release) runs are never cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build_wheels: name: Build wheels on ${{ matrix.os }} + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -53,6 +65,8 @@ jobs: build_sdist: name: Build source distribution + # Skip build CI for draft PRs; still runs on push-to-main and non-draft PRs. + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest steps: From 5438a1587ee7a7fb555d3cc27842e8b08ab3f274 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 22:21:51 -0500 Subject: [PATCH 2/3] ci: fix concurrency fallback and draft->ready trigger for build workflows Use github.run_id (unique per run) instead of github.ref as the concurrency-group fallback so non-PR runs (push, release) never serialize or cancel each other. Add ready_for_review to the pull_request trigger types so marking a draft PR ready triggers a build (the draft guard otherwise skips draft PRs, leaving them un-built until the next push). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 12 +++++++++--- .github/workflows/build_libraries.yml | 10 ++++++++-- .github/workflows/build_wheels.yml | 10 ++++++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4bec6e36..18aba0af6 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,10 @@ name: Build on: pull_request: branches: [main] + # ready_for_review is included so that marking a draft PR "Ready for + # review" triggers a build (the draft guard below skips draft PRs); + # it is not in GitHub's default set (opened/synchronize/reopened). + types: [opened, synchronize, reopened, ready_for_review] paths: - 'components/**' - 'external/**' @@ -13,11 +17,13 @@ on: # Supersede in-progress runs: a new commit on the same PR cancels the earlier, # now-stale build. Keyed by the workflow (so it never cross-cancels other # workflows) + the PR number, which is globally unique - unlike the head branch -# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# name, two fork PRs can't collide on it. For non-PR events (push, release) we +# fall back to github.run_id, which is unique per run, so those runs each get +# their own group and never serialize or cancel each other. # cancel-in-progress is gated to pull_request events only so that push-to-main -# runs are never cancelled (distinct refs also get distinct groups anyway). +# runs are never cancelled. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: diff --git a/.github/workflows/build_libraries.yml b/.github/workflows/build_libraries.yml index 24b241f3a..e6262654a 100644 --- a/.github/workflows/build_libraries.yml +++ b/.github/workflows/build_libraries.yml @@ -3,6 +3,10 @@ name: Build Host C++ / Python Libraries on: pull_request: branches: [main] + # ready_for_review is included so that marking a draft PR "Ready for + # review" triggers a build (the draft guard below skips draft PRs); + # it is not in GitHub's default set (opened/synchronize/reopened). + types: [opened, synchronize, reopened, ready_for_review] push: branches: [main] release: @@ -12,11 +16,13 @@ on: # Supersede in-progress runs: a new commit on the same PR cancels the earlier, # now-stale build. Keyed by the workflow (so it never cross-cancels other # workflows) + the PR number, which is globally unique - unlike the head branch -# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# name, two fork PRs can't collide on it. For non-PR events (push, release) we +# fall back to github.run_id, which is unique per run, so those runs each get +# their own group and never serialize or cancel each other. # cancel-in-progress is gated to pull_request events only so that push-to-main # (and release) runs are never cancelled. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 7cac77832..122b731af 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -3,6 +3,10 @@ name: Build Python Wheels on: pull_request: branches: [main] + # ready_for_review is included so that marking a draft PR "Ready for + # review" triggers a build (the draft guard below skips draft PRs); + # it is not in GitHub's default set (opened/synchronize/reopened). + types: [opened, synchronize, reopened, ready_for_review] paths: - 'lib/**' - 'components/**' @@ -22,11 +26,13 @@ permissions: # Supersede in-progress runs: a new commit on the same PR cancels the earlier, # now-stale build. Keyed by the workflow (so it never cross-cancels other # workflows) + the PR number, which is globally unique - unlike the head branch -# name, two fork PRs can't collide on it. Falls back to github.ref otherwise. +# name, two fork PRs can't collide on it. For non-PR events (push, release) we +# fall back to github.run_id, which is unique per run, so those runs each get +# their own group and never serialize or cancel each other. # cancel-in-progress is gated to pull_request events only so that push-to-main # (and release) runs are never cancelled. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: From f27756906669eaeb4c05620ab7a024ae1e754e2a Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 15 Aug 2026 09:32:57 -0500 Subject: [PATCH 3/3] ci: add least-privilege workflow permissions (CodeQL) Add a top-level `permissions: contents: read` block to build.yml and build_libraries.yml to satisfy the CodeQL actions/missing-workflow-permissions query. These jobs only check out the repo and build/upload artifacts, so read-only access is sufficient. build_wheels.yml already carries the least-privilege default plus a job-level id-token: write override on the PyPI trusted-publishing job. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 5 +++++ .github/workflows/build_libraries.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18aba0af6..f2157acc9 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,11 @@ on: - '!**/idf_component.yml' - '!**/README.md' +# Least-privilege default: these jobs only check out the repo and build/upload +# artifacts, so read-only access to repository contents is sufficient. +permissions: + contents: read + # Supersede in-progress runs: a new commit on the same PR cancels the earlier, # now-stale build. Keyed by the workflow (so it never cross-cancels other # workflows) + the PR number, which is globally unique - unlike the head branch diff --git a/.github/workflows/build_libraries.yml b/.github/workflows/build_libraries.yml index e6262654a..8c4806103 100644 --- a/.github/workflows/build_libraries.yml +++ b/.github/workflows/build_libraries.yml @@ -13,6 +13,11 @@ on: types: [published] workflow_dispatch: +# Least-privilege default: these jobs only check out the repo and build/upload +# artifacts, so read-only access to repository contents is sufficient. +permissions: + contents: read + # Supersede in-progress runs: a new commit on the same PR cancels the earlier, # now-stale build. Keyed by the workflow (so it never cross-cancels other # workflows) + the PR number, which is globally unique - unlike the head branch