Summary
MonthBar is the #1 CI spender across all BrightDigit repos at $39.98/month (76% of total spend). The highest-impact changes are: adding concurrency groups to cancel stacked runs (clearly happening given 3,064 Linux minutes last month), dynamic matrix gating to limit feature branches, and evaluating/cutting Windows builds ($7.07/month for a macOS menu bar app).
Current cost: $39.98/month ($19.19 Linux + $7.07 Windows + $0.79 claude-code-review.yml). macOS is self-hosted ($0). Estimated savings: $15–19/month.
Changes to make
There is no pre-made replacement workflow file for MonthBar. Read the existing .github/workflows/monthbar.yml first, then apply the patterns below.
1. Add concurrency groups (highest-impact change)
Add at the top level of the workflow (after the on: block):
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}cancel-in-progress: trueWith 3,064 Linux minutes in March, runs are clearly stacking. This single change may save $5–8/month.
2. Add paths-ignore to push and pull_request triggers
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
3. Add a configure job for dynamic matrix gating
Insert this job before build-ubuntu (adjust matrix values to match what's currently in monthbar.yml):
configure:
name: Configure Matrixruns-on: ubuntu-latestoutputs:
full-matrix: ${{ steps.check.outputs.full }}cross-platform: ${{ steps.check.outputs.cross_platform }}ubuntu-os: ${{ steps.matrix.outputs.ubuntu-os }}ubuntu-swift: ${{ steps.matrix.outputs.ubuntu-swift }}ubuntu-type: ${{ steps.matrix.outputs.ubuntu-type }}steps:
- id: checkname: Determine matrix scoperun: | FULL=false CROSS_PLATFORM=false REF="${{ github.ref }}" EVENT="${{ github.event_name }}" BASE_REF="${{ github.base_ref }}" if [[ "$REF" == "refs/heads/main" ]]; then FULL=true elif [[ "$REF" =~ ^refs/heads/v?[0-9]+\.[0-9]+\.[0-9]+ ]]; then FULL=true elif [[ "$EVENT" == "workflow_dispatch" ]]; then FULL=true elif [[ "$EVENT" == "pull_request" ]]; then if [[ "$BASE_REF" == "main" || "$BASE_REF" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+ ]]; then FULL=true CROSS_PLATFORM=true fi fi echo "full=$FULL" >> "$GITHUB_OUTPUT" echo "cross_platform=$CROSS_PLATFORM" >> "$GITHUB_OUTPUT" echo "Full matrix: $FULL, Cross-platform: $CROSS_PLATFORM (ref=$REF, event=$EVENT, base_ref=$BASE_REF)" - id: matrixname: Build matrix valuesrun: | if [[ "${{ steps.check.outputs.full }}" == "true" ]]; then # Use whatever OS/Swift versions are currently in the full matrix echo 'ubuntu-os=["noble","jammy"]' >> "$GITHUB_OUTPUT" echo 'ubuntu-swift=[{"version":"6.2"},{"version":"6.3","nightly":true}]' >> "$GITHUB_OUTPUT" echo 'ubuntu-type=[""]' >> "$GITHUB_OUTPUT" else # Minimal: noble + Swift 6.2 only echo 'ubuntu-os=["noble"]' >> "$GITHUB_OUTPUT" echo 'ubuntu-swift=[{"version":"6.2"}]' >> "$GITHUB_OUTPUT" echo 'ubuntu-type=[""]' >> "$GITHUB_OUTPUT" fiNote: Adjust the ubuntu-os, ubuntu-swift, and ubuntu-type full-matrix values to match what's currently in monthbar.yml. The minimal matrix (noble + Swift 6.2) is standard across repos.
4. Gate build-ubuntu to use dynamic matrix
Update build-ubuntu to depend on configure and use its outputs:
build-ubuntu:
needs: configurestrategy:
matrix:
os: ${{ fromJSON(needs.configure.outputs.ubuntu-os) }}swift: ${{ fromJSON(needs.configure.outputs.ubuntu-swift) }}type: ${{ fromJSON(needs.configure.outputs.ubuntu-type) }}Keep all existing steps as-is; only change the needs and strategy.matrix values.
5. Gate Windows builds
MonthBar is a macOS menu bar app — evaluate whether Windows CI provides value. Two options:
Option A (recommended if Windows has no consumers): Remove the Windows job entirely.
Option B (if cross-platform validation is desired): Gate the Windows job to PRs targeting main/semver only:
build-windows:
needs: configureif: ${{ needs.configure.outputs.cross-platform == 'true' }}# Keep a single job: windows-2025, Swift 6.2 onlyRead the existing Windows job and decide which option applies. If MonthBar has no Windows/Linux package consumers (it's a menu bar app), Option A saves $7.07/month immediately.
6. Split build-macos into two jobs (if it has a broad matrix)
If build-macos currently includes macOS, tvOS, visionOS, watchOS, and multiple Xcode versions, split it:
build-macos — always runs: SPM + iOS + watchOS (self-hosted, $0)build-macos-platforms — full-matrix only (needs.configure.outputs.full-matrix == 'true'): macOS, tvOS, visionOS, older Xcode versions
If the macOS matrix is already minimal, skip this split.
7. Update the lint job
Change the if condition to:
if: ${{ !cancelled() && !failure() }}And add any new jobs (build-macos-platforms, build-windows if kept) to the needs list.
8. Add .github/workflows/cleanup-caches.yml
Create this new file:
name: Cleanup Branch Cacheson:
delete:
jobs:
cleanup:
runs-on: ubuntu-latestpermissions:
actions: writesteps:
- name: Cleanup caches for deleted branchuses: actions/github-script@v7with:
script: | const ref = `refs/heads/${context.payload.ref}`; const caches = await github.rest.actions.getActionsCacheList({ owner: context.repo.owner, repo: context.repo.repo, ref: ref, }); for (const cache of caches.data.actions_caches) { console.log(`Deleting cache: ${cache.key}`); await github.rest.actions.deleteActionsCacheById({ owner: context.repo.owner, repo: context.repo.repo, cache_id: cache.id, }); } console.log(`Deleted ${caches.data.actions_caches.length} cache(s) for ${ref}`);9. Add .github/workflows/cleanup-pr-caches.yml
Create this new file:
name: Cleanup PR Cacheson:
pull_request:
types: [closed]jobs:
cleanup:
runs-on: ubuntu-latestpermissions:
actions: writesteps:
- name: Cleanup caches for closed PRuses: actions/github-script@v7with:
script: | const pr = context.payload.pull_request.number; const refs = [ `refs/pull/${pr}/merge`, `refs/pull/${pr}/head`, ]; for (const ref of refs) { const caches = await github.rest.actions.getActionsCacheList({ owner: context.repo.owner, repo: context.repo.repo, ref, }); for (const cache of caches.data.actions_caches) { console.log(`Deleting cache: ${cache.key} (${ref})`); await github.rest.actions.deleteActionsCacheById({ owner: context.repo.owner, repo: context.repo.repo, cache_id: cache.id, }); } console.log(`Deleted ${caches.data.actions_caches.length} cache(s) for ${ref}`); }Impact
- Concurrency: cancels stacked runs — likely the single biggest saving given 3,064 Linux minutes last month
- Dynamic matrix: feature branch pushes drop from ~15+ jobs to ~3–4
- Windows removal/gating: $7.07/month eliminated or reduced to PRs only
- Estimated total savings: $15–19/month (37–47% reduction)
Checklist
Summary
MonthBar is the #1 CI spender across all BrightDigit repos at $39.98/month (76% of total spend). The highest-impact changes are: adding concurrency groups to cancel stacked runs (clearly happening given 3,064 Linux minutes last month), dynamic matrix gating to limit feature branches, and evaluating/cutting Windows builds ($7.07/month for a macOS menu bar app).
Current cost: $39.98/month ($19.19 Linux + $7.07 Windows + $0.79 claude-code-review.yml). macOS is self-hosted ($0). Estimated savings: $15–19/month.
Changes to make
There is no pre-made replacement workflow file for MonthBar. Read the existing
.github/workflows/monthbar.ymlfirst, then apply the patterns below.1. Add concurrency groups (highest-impact change)
Add at the top level of the workflow (after the
on:block):With 3,064 Linux minutes in March, runs are clearly stacking. This single change may save $5–8/month.
2. Add paths-ignore to push and pull_request triggers
3. Add a
configurejob for dynamic matrix gatingInsert this job before
build-ubuntu(adjust matrix values to match what's currently in monthbar.yml):4. Gate build-ubuntu to use dynamic matrix
Update
build-ubuntuto depend onconfigureand use its outputs:Keep all existing steps as-is; only change the
needsandstrategy.matrixvalues.5. Gate Windows builds
MonthBar is a macOS menu bar app — evaluate whether Windows CI provides value. Two options:
Option A (recommended if Windows has no consumers): Remove the Windows job entirely.
Option B (if cross-platform validation is desired): Gate the Windows job to PRs targeting main/semver only:
Read the existing Windows job and decide which option applies. If MonthBar has no Windows/Linux package consumers (it's a menu bar app), Option A saves $7.07/month immediately.
6. Split build-macos into two jobs (if it has a broad matrix)
If
build-macoscurrently includes macOS, tvOS, visionOS, watchOS, and multiple Xcode versions, split it:build-macos— always runs: SPM + iOS + watchOS (self-hosted, $0)build-macos-platforms— full-matrix only (needs.configure.outputs.full-matrix == 'true'): macOS, tvOS, visionOS, older Xcode versionsIf the macOS matrix is already minimal, skip this split.
7. Update the lint job
Change the
ifcondition to:And add any new jobs (
build-macos-platforms,build-windowsif kept) to theneedslist.8. Add
.github/workflows/cleanup-caches.ymlCreate this new file:
9. Add
.github/workflows/cleanup-pr-caches.ymlCreate this new file:
Impact
Checklist
.github/workflows/monthbar.ymlto understand the current job structureconcurrencyblock at the top levelpaths-ignoretopushandpull_requesttriggersconfigurejob (adjust full-matrix values to match current Ubuntu matrix)build-ubuntuto useneeds: configureand dynamic matrix outputsbuild-macosif the current macOS matrix is broadlintjob condition andneeds.github/workflows/cleanup-caches.ymlwith the content above.github/workflows/cleanup-pr-caches.ymlwith the content abovemainand verify full matrix fires