ci: path-filter per-board builds + nightly safety-net sweep (#835)#836
Conversation
The 79 build-<board>.yml workflows previously fired on every push/PR
to main, producing heavy back-pressure for changes that touch a single
crate or unrelated code. This change biases per-board builds toward
NOT running:
* Each build-<board>.yml `on:` block now has a `paths:` allowlist
covering the board's own test sketch, its family code (e.g. all
LPC boards include `crates/fbuild-build/src/{nxplpc,generic_arm}/**`),
and a conservative list of common-code paths whose changes still
force every board to build (safety net).
* The `on:` blocks and a new `nightly-platforms.yml` are rendered
from two SOT files (`ci/board_families.json`,
`ci/ci_common_paths.txt`) by `ci/render_workflows.py`. A new
`ci-workflow-drift.yml` runs the renderer with `--check` on every
PR to enforce no drift.
* `nightly-platforms.yml` runs all 79 builds at 09:00 UTC daily
(~01:00 PST winter / 02:00 PDT summer). A `guard` job exits
cleanly when no commits landed in the last 24h, so quiet days
cost nothing. `workflow_dispatch` exposes a `force` input to
bypass the guard for manual reruns.
Per-board workflows now also expose `workflow_call: {}` so the
nightly fan-out can re-use them.
Documented in docs/DEVELOPMENT.md ("CI: per-board build triggers").
Closes #835
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a Python renderer ( ChangesCI Path-Filter + Nightly Platform Sweep
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci-workflow-drift.yml:
- Around line 23-26: The workflow uses an invalid checkout action version and is
missing the recommended security settings. Update the `actions/checkout` step in
the CI workflow to a valid stable release, add `persist-credentials: false` to
that step, and add a restrictive workflow-level `permissions` block limited to
`contents: read` since `Verify rendered workflows match SOT` only needs read
access.
In @.github/workflows/nightly-platforms.yml:
- Around line 21-48: The nightly-platforms workflow is using the default broad
GitHub token permissions; add a restrictive permissions block so the guard job
and reusable-workflow calls only have the access they need. Update the top-level
workflow configuration in nightly-platforms.yml (near the guard job definition)
to set contents: read, or apply the same restriction per job if needed, since
the workflow only checks out code and triggers reusable workflows.
In `@ci/board_families.json`:
- Around line 59-60: The alias entries in board_families are still causing
duplicate fan-out for the same board. Update the generation logic and/or the
affected board_families entries so only one canonical workflow is included in
automatic push/PR/nightly triggers, and keep alias workflow files out of the
fan-out path; use the existing workflow, workflow_name, test_dir, env_name, and
family fields to identify the duplicate Nano Every-style entries and apply the
same deduplication to the other listed aliases.
In `@ci/render_workflows.py`:
- Around line 247-254: The drift check in render_workflows.py only regenerates
the workflow on: section, so changes in the declared board metadata can still
drift unnoticed. Update the rendering flow around the board loop and
render_on_block/rewrite logic to also render or verify the jobs.build.with block
from the same board_families metadata, covering workflow_name, test_dir,
env_name, and firmware_ext. Make sure the check path compares the generated
jobs.build.with values against the existing workflow content so --check catches
mismatches, not just on: block changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4e13be23-38cf-43df-b85b-5815dc512129
📒 Files selected for processing (86)
.github/workflows/build-apollo3_red.yml.github/workflows/build-apollo3_thing_explorable.yml.github/workflows/build-atmega8.yml.github/workflows/build-atmega8a.yml.github/workflows/build-attiny1604.yml.github/workflows/build-attiny1616.yml.github/workflows/build-attiny4313.yml.github/workflows/build-attiny85.yml.github/workflows/build-attiny88.yml.github/workflows/build-blackpill.yml.github/workflows/build-bluepill.yml.github/workflows/build-ch32l103.yml.github/workflows/build-ch32v003.yml.github/workflows/build-ch32v006.yml.github/workflows/build-ch32v103.yml.github/workflows/build-ch32v203.yml.github/workflows/build-ch32v208.yml.github/workflows/build-ch32v303.yml.github/workflows/build-ch32v307.yml.github/workflows/build-ch32x035.yml.github/workflows/build-due.yml.github/workflows/build-esp32c2.yml.github/workflows/build-esp32c3.yml.github/workflows/build-esp32c5.yml.github/workflows/build-esp32c6.yml.github/workflows/build-esp32dev.yml.github/workflows/build-esp32h2.yml.github/workflows/build-esp32p4.yml.github/workflows/build-esp32s2.yml.github/workflows/build-esp32s3.yml.github/workflows/build-esp8266.yml.github/workflows/build-giga-r1.yml.github/workflows/build-leonardo.yml.github/workflows/build-lpc804.yml.github/workflows/build-lpc845.yml.github/workflows/build-lpc845brk.yml.github/workflows/build-lpcxpresso804.yml.github/workflows/build-lpcxpresso845max.yml.github/workflows/build-matrix_portal_m4.yml.github/workflows/build-mgm240.yml.github/workflows/build-nano-every.yml.github/workflows/build-nano_every.yml.github/workflows/build-nice_nano_nrf52840.yml.github/workflows/build-nrf52840-sense.yml.github/workflows/build-nrf52840_dk.yml.github/workflows/build-nrfmicro_nrf52840.yml.github/workflows/build-nucleo-f429zi.yml.github/workflows/build-nucleo-f439zi.yml.github/workflows/build-nucleo_f429zi.yml.github/workflows/build-nucleo_f439zi.yml.github/workflows/build-qtpy_m0.yml.github/workflows/build-rp2040.yml.github/workflows/build-rp2350.yml.github/workflows/build-rpipico.yml.github/workflows/build-rpipico2.yml.github/workflows/build-sam3x8e_due.yml.github/workflows/build-samd21.yml.github/workflows/build-samd21_zero.yml.github/workflows/build-samd51j.yml.github/workflows/build-samd51p.yml.github/workflows/build-stm32f103c8.yml.github/workflows/build-stm32f103cb.yml.github/workflows/build-stm32f103tb.yml.github/workflows/build-stm32f411ce.yml.github/workflows/build-stm32h747xi.yml.github/workflows/build-supermini_nrf52840.yml.github/workflows/build-teensy30.yml.github/workflows/build-teensy31.yml.github/workflows/build-teensy32.yml.github/workflows/build-teensy35.yml.github/workflows/build-teensy36.yml.github/workflows/build-teensy40.yml.github/workflows/build-teensy41.yml.github/workflows/build-teensylc.yml.github/workflows/build-thingplusmatter.yml.github/workflows/build-tinystm.yml.github/workflows/build-uno-r4-wifi.yml.github/workflows/build-uno.yml.github/workflows/build-uno_r4_wifi.yml.github/workflows/ci-workflow-drift.yml.github/workflows/nightly-platforms.ymlci/README.mdci/board_families.jsonci/ci_common_paths.txtci/render_workflows.pydocs/DEVELOPMENT.md
| - uses: actions/checkout@v6 | ||
| - uses: astral-sh/setup-uv@v3 | ||
| - name: Verify rendered workflows match SOT | ||
| run: uv run --no-project python ci/render_workflows.py --check |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Fix actions/checkout@v6 — v6 does not exist.
actions/checkout@v6 will fail at runtime; the latest stable release is v4. Also address the zizmor warnings:
- Add
persist-credentials: falsetoactions/checkout. - Add a restrictive
permissions:block (this workflow only needscontents: read).
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
- uses: astral-sh/setup-uv@v3
- name: Verify rendered workflows match SOT
run: uv run --no-project python ci/render_workflows.py --check
+
+permissions:
+ contents: read📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v3 | |
| - name: Verify rendered workflows match SOT | |
| run: uv run --no-project python ci/render_workflows.py --check | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - uses: astral-sh/setup-uv@v3 | |
| - name: Verify rendered workflows match SOT | |
| run: uv run --no-project python ci/render_workflows.py --check | |
| permissions: | |
| contents: read |
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci-workflow-drift.yml around lines 23 - 26, The workflow
uses an invalid checkout action version and is missing the recommended security
settings. Update the `actions/checkout` step in the CI workflow to a valid
stable release, add `persist-credentials: false` to that step, and add a
restrictive workflow-level `permissions` block limited to `contents: read` since
`Verify rendered workflows match SOT` only needs read access.
Source: Linters/SAST tools
| guard: | ||
| name: Guard (skip on quiet days) | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| should_run: ${{ steps.check.outputs.should_run }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 0 | ||
| - id: check | ||
| env: | ||
| FORCE: ${{ inputs.force }} | ||
| run: | | ||
| if [ "$FORCE" = "true" ]; then | ||
| echo "force=true -- running nightly sweep regardless of commit activity" | ||
| echo "should_run=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| # Scheduled runs check out the default branch's HEAD; on | ||
| # workflow_dispatch from a feature branch this checks that | ||
| # branch instead, which is the right behavior for manual runs. | ||
| if [ -z "$(git log --since='24 hours ago' --oneline HEAD)" ]; then | ||
| echo "No commits in the last 24h -- skipping nightly platform sweep" | ||
| echo "should_run=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "Recent commits found -- running full nightly sweep" | ||
| echo "should_run=true" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Add restrictive permissions: block.
The workflow and all reusable-workflow jobs run with default broad permissions. Add permissions: contents: read at workflow level, or restrict per-job as appropriate. Since this workflow only needs to checkout and call reusable workflows, contents: read is sufficient.
permissions:
contents: read🧰 Tools
🪛 zizmor (1.26.1)
[warning] 27-29: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 21-48: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/nightly-platforms.yml around lines 21 - 48, The
nightly-platforms workflow is using the default broad GitHub token permissions;
add a restrictive permissions block so the guard job and reusable-workflow calls
only have the access they need. Update the top-level workflow configuration in
nightly-platforms.yml (near the guard job definition) to set contents: read, or
apply the same restriction per job if needed, since the workflow only checks out
code and triggers reusable workflows.
Source: Linters/SAST tools
| { "workflow": "build-nano-every.yml", "workflow_name": "Arduino Nano Every", "test_dir": "tests/platform/nano_every", "env_name": "nano_every", "firmware_ext": "hex", "family": "avr" }, | ||
| { "workflow": "build-nano_every.yml", "workflow_name": "Arduino Nano Every", "test_dir": "tests/platform/nano_every", "env_name": "nano_every", "firmware_ext": "hex", "family": "avr" }, |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
These alias entries still fan out the same board twice.
Each pair shares the same test_dir, env_name, firmware_ext, and family, so one sketch/family/common-path change will enqueue both workflows and the nightly sweep will call both. That preserves redundant CI load on the exact paths this PR is trying to decongest. Please canonicalize one auto-triggered workflow per board, or keep alias workflow files out of the generated push/PR/nightly fan-out.
Also applies to: 65-68, 95-97
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ci/board_families.json` around lines 59 - 60, The alias entries in
board_families are still causing duplicate fan-out for the same board. Update
the generation logic and/or the affected board_families entries so only one
canonical workflow is included in automatic push/PR/nightly triggers, and keep
alias workflow files out of the fan-out path; use the existing workflow,
workflow_name, test_dir, env_name, and family fields to identify the duplicate
Nano Every-style entries and apply the same deduplication to the other listed
aliases.
| for board in boards: | ||
| path = WORKFLOWS_DIR / board["workflow"] | ||
| old = path.read_text(encoding="utf-8") | ||
| new_on = render_on_block(board, families, common_paths) | ||
| new = rewrite(old, new_on) | ||
| write_if_changed(path, new, args.check, drift, updated) | ||
|
|
||
| write_if_changed(NIGHTLY_PATH, render_nightly(boards), args.check, drift, updated) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
The drift gate only enforces half of the declared SOT.
This loop regenerates on: blocks only. ci/board_families.json already carries workflow_name, test_dir, env_name, and firmware_ext, but Line 97 there already disagrees with .github/workflows/build-uno_r4_wifi.yml Line 121 on workflow_name. The same silent drift can happen for test-dir, env-name, and firmware-ext without --check noticing. If these fields are part of the SOT, render or at least verify the jobs.build.with block from the same metadata.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ci/render_workflows.py` around lines 247 - 254, The drift check in
render_workflows.py only regenerates the workflow on: section, so changes in the
declared board metadata can still drift unnoticed. Update the rendering flow
around the board loop and render_on_block/rewrite logic to also render or verify
the jobs.build.with block from the same board_families metadata, covering
workflow_name, test_dir, env_name, and firmware_ext. Make sure the check path
compares the generated jobs.build.with values against the existing workflow
content so --check catches mismatches, not just on: block changes.
Audit found 10 workflows missing a manual-trigger entry. Adds
`workflow_dispatch: {}` to:
- add-to-project.yml (manual re-add of a missed issue/PR)
- check-macos.yml, check-ubuntu.yml, check-windows.yml
- docs.yml, dylint.yml, fmt.yml, lint-subprocess.yml,
msrv.yml, validate-boards.yml
Reusable-workflow templates (template_build.yml,
template_native_build.yml) are intentionally left without
workflow_dispatch -- they take required inputs from the caller
and have no sensible standalone defaults.
Every other workflow under .github/workflows/ already declared
either workflow_dispatch (per-board build-*.yml after #836,
nightly-platforms.yml, ci-workflow-drift.yml, release-auto.yml,
update-data.yml, hw-ci.yml, etc.) or workflow_call (templates).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #835.
Summary
Relieves CI back-pressure: the 79
build-<board>.ymlworkflows no longer fire on every push/PR. Each one now has apaths:allowlist covering (a) its own test sketch, (b) its family code incrates/fbuild-build/src/<family>/**, and (c) a conservative common-path list. A newnightly-platforms.ymlruns everything once a day with a guard that skips quiet days.What changed
ci/board_families.json— single source of truth: per-board metadata + family → crate-path mapping.ci/ci_common_paths.txt— paths whose changes force-run every board (compiler/linker,pipeline/, shared crates,Cargo.lock, etc.).ci/render_workflows.py— renders theon:block of everybuild-<board>.yml(between sentinel comments) and the entirenightly-platforms.yml.--checkmode exits 1 on drift..github/workflows/build-*.yml(79 files) —on:blocks regenerated. Each gainsworkflow_call: {}so nightly can re-use them, and apaths:allowlist on push + pull_request..github/workflows/nightly-platforms.yml(new) — cron0 9 * * *UTC (~01:00 PST winter / 02:00 PDT summer) +workflow_dispatchwith aforceinput. Aguardjob runsgit log --since='24 hours ago' HEADand short-circuits the fan-out when there are no recent commits..github/workflows/ci-workflow-drift.yml(new) — runsrender_workflows.py --checkon every PR.docs/DEVELOPMENT.md+ci/README.md— documents the trigger contract and how to opt back in.Acceptance criteria
crates/fbuild-build/src/nxplpc/**triggers all LPC builds and nothing else (verified bynxplpc/**+generic_arm/**appearing in everybuild-lpc*.ymland no non-LPC board).tests/platform/esp32dev/**triggersbuild-esp32dev.ymlonly.crates/fbuild-cli/**,crates/fbuild-build/src/compiler.rs) triggers all 79 boards viaci/ci_common_paths.txt.nightly-platforms.ymlruns cron-scheduled, guard-skips quiet days.ci/board_families.json+ci/ci_common_paths.txt);ci-workflow-drift.ymlfails on drift.check-{ubuntu,macos,windows}.yml,crate-gate.yml,dylint.yml,fmt.yml,msrv.yml,loc-gate.yml,lint-subprocess.yml,docs.ymlunchanged.release-auto.ymlunchanged.docs/DEVELOPMENT.md("CI: per-board build triggers").Test plan
uv run --no-project python ci/render_workflows.py— re-renders cleanly, zero changes on second run (idempotent).uv run --no-project python ci/render_workflows.py --check— exit 0 on the committed tree.yaml.safe_loadparsesnightly-platforms.yml,ci-workflow-drift.yml, and a sampled subset of the renderedbuild-*.ymlfiles (lpc804,esp32dev,uno,teensy41,stm32h747xi) cleanly.check-*,fmt,dylint,crate-gate,loc-gate,lint-subprocess,ci-workflow-drift) all run; platform builds only run if a common-path file is in the diff (this PR touchesci/render_workflows.pyandci/board_families.json, which are in the common list, so the safety net will fire this PR's builds and validate the rewritten triggers).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores