ci: scope native/wheel/dashboard jobs to relevant paths (unstarve the queue) (#2155)

## Problem
CI's `changes` paths-filter is too coarse: both `code` and `e2e` include
the catch-all `headroom/**`, so **any** Python change fires the full
matrix — including the scarce-runner hogs: `build-wheel-windows`,
`macos-native-wrapper`, `windows-native-wrapper`, `docker-native-e2e`.
macOS runners cap at 5 on the Free plan, so a trivial Python PR burns
the scarcest resource. `init-e2e`/`wrap-e2e` trigger on `headroom/**`
too.

## Change (CI config only — no product code)
- **ci.yml:** split `changes` into `code` / `native` / `dashboard` /
`packaging`. Re-gate:
  | job | before | after |
  |-----|--------|-------|
  | `build-wheel-windows` | code | `packaging` |
  | `test-dashboard-ui` | code | `dashboard` |
  | `docker-native-e2e` | e2e | `native` |
  | `windows-native-wrapper` | e2e | `native` |
  | `macos-native-wrapper` | e2e | `native` |
- **init-e2e / wrap-e2e:** scoped off `headroom/**` onto the
`cli`/`install`/`providers`/`rtk` subpaths the flows exercise (mirrors
the existing `*-native-e2e` workflows).

## Safety
- Verified no job `needs:` any of the re-gated jobs, so no dependency
chain breaks. `test-dashboard-ui` still gets `build-wheel` (dashboard
files are under `headroom/` ⟹ `code` too).
- Job-level `if:` skips resolve as **neutral** (not failing), so this is
safe even if branch protection is added.
- Full matrix still runs on any `.github/workflows/**` change and on
push to `main` (post-merge safety net).

## Net
A pure-Python logic PR skips ~7-9 jobs incl. all 3 native platform
builds — freeing the macOS/Windows caps that were starving the queue.
This commit is contained in:
Tejas Chopra 2026-07-13 23:50:26 -04:00 committed by GitHub
parent b0afee85b3
commit 58d3445d23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 10 deletions

View file

@ -43,7 +43,9 @@ jobs:
timeout-minutes: 5
outputs:
code: ${{ steps.filter.outputs.code }}
e2e: ${{ steps.filter.outputs.e2e }}
native: ${{ steps.filter.outputs.native }}
dashboard: ${{ steps.filter.outputs.dashboard }}
packaging: ${{ steps.filter.outputs.packaging }}
workflows: ${{ steps.filter.outputs.workflows }}
steps:
- uses: actions/checkout@v7
@ -61,14 +63,41 @@ jobs:
- 'tests/**'
- 'scripts/**'
- '.github/workflows/**'
e2e:
- 'headroom/**'
# native = anything that can change the compiled wrapper, the native
# install flow, or the docker image (drives the scarce macOS/Windows
# runners + docker E2E). A pure-Python logic change hits none of these.
native:
- 'headroom/cli/**'
- 'headroom/install/**'
- 'headroom/providers/**'
- 'headroom/rtk/**'
- 'crates/**'
- '**/*.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'docker/**'
- 'Dockerfile'
- 'e2e/**'
- 'scripts/install*'
- 'pyproject.toml'
- '.github/workflows/**'
dashboard:
- 'headroom/dashboard/**'
- '.github/workflows/**'
# packaging = anything that changes how the wheel is built (so the
# cross-platform wheel build only reruns when the build actually changes).
packaging:
- 'pyproject.toml'
- 'Cargo.toml'
- 'Cargo.lock'
- 'uv.lock'
- 'rust-toolchain.toml'
- 'crates/**'
- '**/*.rs'
- 'scripts/**'
- 'MANIFEST.in'
- '.github/workflows/**'
workflows:
- '.github/workflows/**'
@ -122,7 +151,7 @@ jobs:
build-wheel-windows:
needs: changes
if: needs.changes.outputs.code == 'true'
if: needs.changes.outputs.packaging == 'true'
runs-on: windows-latest
timeout-minutes: 45
steps:
@ -354,7 +383,7 @@ jobs:
test-dashboard-ui:
needs: [changes, build-wheel]
if: needs.changes.outputs.code == 'true'
if: needs.changes.outputs.dashboard == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
@ -474,7 +503,7 @@ jobs:
docker-native-e2e:
needs: changes
if: needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.native == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
@ -518,7 +547,7 @@ jobs:
windows-native-wrapper:
needs: changes
if: needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.native == 'true'
runs-on: windows-latest
timeout-minutes: 20
steps:
@ -535,7 +564,7 @@ jobs:
macos-native-wrapper:
needs: changes
if: needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.native == 'true'
runs-on: macos-latest
timeout-minutes: 20
steps:

View file

@ -3,8 +3,12 @@ name: Init E2E
on:
pull_request:
branches: [main]
# Scoped to what the `headroom init` flow actually exercises (mirrors
# init-native-e2e), not all of headroom/** — a pure-Python logic change
# elsewhere shouldn't spin up a docker init E2E.
paths:
- 'headroom/**'
- 'headroom/cli/**'
- 'headroom/install/**'
- 'crates/**'
- 'docker/**'
- 'Dockerfile'

View file

@ -3,8 +3,13 @@ name: Wrap E2E
on:
pull_request:
branches: [main]
# Scoped to what the `headroom wrap` flow actually exercises (mirrors
# wrap-native-e2e), not all of headroom/** — a pure-Python logic change
# elsewhere shouldn't spin up a docker wrap E2E.
paths:
- 'headroom/**'
- 'headroom/cli/**'
- 'headroom/providers/**'
- 'headroom/rtk/**'
- 'crates/**'
- 'docker/**'
- 'Dockerfile'