ci: run gates on every branch, one publisher per branch - #50
Merged
Conversation
Both firmware workflows that compile code were branch-restricted, so a push to a milestone or fix branch built nothing at all. The AVR gates only ran on main, and the ARM gate only on beta -- a break was found at merge time rather than when it was written. A PR targeting `beta` got no AVR CI whatsoever, which is exactly what happened to the CAP-02 firmware PR. build.yml push/pull_request branch filters removed. Three steps at the bottom of the job MUTATE the repo or publish -- the version bump, the git-auto-commit that pushes it BACK to the branch it ran on, and the release upload with make_latest: true. Their safety was implied entirely by the main-only trigger. Each now carries an explicit `if: github.event_name == 'push' && github.ref == 'refs/heads/main'`. Without those, removing the filter would have landed a version-bump commit on every feature branch (itself a push, so it would re-trigger the workflow) and published a spurious "latest" stable release from it. update_version.py rewrites the TRACKED include/version.h in place, so skipping it off-main is harmless: the build uses the committed version. `pio run` deliberately stays outside the boundary -- compiling every AVR env is the point of running elsewhere -- and stays after the bump so the published .hex on main still carries the bumped version. py32f071.yml push filter dropped. This workflow publishes nothing: its two upload-artifact steps attach ephemeral output to the run, not to a release. Keeping them on every branch is deliberate -- a .hex is most useful to download while the work is still on a branch. The release ASSET continues to come from beta-build.yml alone. beta-build.yml is untouched and remains beta-only, so beta is still the only source of pre-release assets. Two details worth flagging: - Both push triggers are `branches: ['**']`, not a bare `push:`. A bare push trigger also fires on TAG pushes, and this project pushes tags across all three repos at milestone close expecting zero CI from them. '**' matches every branch and no tags, preserving that. - Added a `concurrency` group to each. Broadening the triggers multiplies queued runs and a same-repo PR branch now matches both push and pull_request; stale runs are superseded per ref. cancel-in-progress is explicitly FALSE on main and beta, where a half-finished run could leave a tag without its assets. On a beta push build.yml now runs alongside beta-build.yml. That is not pure duplication: build.yml carries three gates beta-build.yml lacks -- the vector catalog validity check, the frame_vectors.h drift gate, and the no-DEV_TOOLS native suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the trigger change, and caught by it. Opening build.yml to every branch made `pytest tests/ -v` run against beta-derived branches for the first time, and it failed at once: FIRESTARTER_RANGE_FORK='5c9160a...' does not resolve to a commit no commit touching 'platform/py32f071/linker/PY32F071xB_FLASH.ld' exists strictly after 40b11eb... Not a code failure. The Phase-123/129 gates -- check_landing_range, the flash-geometry ordering pair, and the PR-45 ancestry pair -- all walk git history: they resolve a fork SHA and assert that a commit touching a given path exists strictly after another commit. actions/checkout@v4 defaults to a shallow depth-1 clone, so those commits are simply absent and the gates fail on the repository shape rather than on anything they were written to check. Why it was invisible: build.yml previously ran on main only, and main lags beta by a wide margin and does not carry those test files at all, so the step passed there for want of anything to run. beta-build.yml -- which does run on beta and therefore does see them -- has carried fetch-depth: 0 all along. This brings build.yml in line. Verified locally: `pytest tests/ -v` is 221 passed in a full-history checkout, which is exactly the condition this restores in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to opening build.yml's trigger. `branches: ['**']` made build.yml --
the STABLE publisher, the workflow holding the make_latest release -- fire on
pushes to beta alongside beta-build.yml.
No double-publish occurred: build.yml's three publish/mutate steps are guarded
to refs/heads/main, so on beta they skipped and only beta-build.yml published.
Verified by simulation. But the arrangement was wrong twice over:
1. Every beta push compiled all AVR targets twice, once per workflow.
2. The stable-release workflow was executing on a branch that must never
produce a stable release. The make_latest step was one `if:` edit away
from firing there. A workflow that cannot run on beta cannot be one edit
away from publishing on beta -- structural exclusion beats a conditional
for something this consequential.
build.yml push trigger is now `branches: ['**', '!beta']`.
PR coverage is deliberately untouched: `pull_request` carries no branch
filter, so a PR targeting beta still runs build.yml in full. Only the direct
push to beta is excluded, and beta-build.yml covers that.
Removing the duplicate build would have silently removed gate coverage too --
build.yml carried three checks beta-build.yml lacked. Those are backfilled
into beta-build.yml so beta ends up strictly better covered, not worse:
- Vector catalog validity check
- Codegen drift gate (frame_vectors.h)
- Run native unit tests (no DEV_TOOLS)
Worth stating plainly: before this commit, a beta push -- the one push that
produces a published artefact -- was the only path NOT checking
frame_vectors.h for codegen drift, and never compiled the no-DEV_TOOLS build.
Resulting one-publisher-per-branch shape, simulated:
push beta -> beta-build.yml (publishes pre-release) + py32f071.yml
push main -> build.yml (publishes stable) + py32f071.yml
push branch -> build.yml (gates + build, publish SKIPPED) + py32f071.yml
PR any base -> build.yml + py32f071.yml
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits. The first opens the triggers; the other two are fixes for things that opening them exposed.
The problem
Both firmware workflows that compile code were branch-restricted, so a push to a milestone or fix branch built nothing. AVR gates ran on
mainonly, the ARM gate onbetaonly. A break surfaced at merge time rather than when it was written — and a PR targetingbetagot no AVR CI at all, which is exactly what happened to #49.Resulting shape
One publisher per branch. No path publishes twice.
99fa244— open the triggers, make publishing explicitThree steps in
build.ymlmutate the repo or publish, and their safety was implied entirely by the main-only trigger. Each now carriesif: github.event_name == 'push' && github.ref == 'refs/heads/main'.This also closes a latent duplicate-publish bug that predates this PR. On
betatoday,build.ymltriggers onpull_request: [main]with all three steps ungated — so every PR intomainwould bump the version, auto-commit onto the PR branch, and cut amake_latestrelease. Merging then republishes the same version derived from the sameinclude/version.h, and the second publish is rejected. Rare enough never to have bitten, but armed. PR runs can no longer publish at all.Verified on a real run: steps 15/16/18 return
skippedon a feature-branch push, while the build still runs.branches: ['**']rather than a barepush:— a bare trigger also fires on tag pushes, and this project pushes tags across three repos at milestone close expecting zero CI.A
concurrencygroup per workflow supersedes stale runs, withcancel-in-progressexplicitly false on main and beta so two rapid pushes to a publishing branch queue instead of racing.665ba99—fetch-depth: 0, caught by the change itselfOpening the trigger made
pytest tests/ -vrun against beta-derived branches for the first time. It failed immediately:Not a code failure. The Phase-123/129 gates walk git history, and
actions/checkout@v4defaults to a shallow depth-1 clone. Invisible before becausebuild.ymlran onmainonly, and main lags beta far enough not to carry those test files — the step passed for want of anything to run.beta-build.ymlhas carriedfetch-depth: 0all along.25235c4— keep the stable publisher off beta, backfill beta's gates['**']madebuild.ymlfire on beta pushes alongsidebeta-build.yml. No double-publish occurred (the guards held), but it rebuilt every AVR target twice, and put the stable-release workflow on a branch that must never cut a stable release — oneif:edit away from doing so. Structural exclusion beats a conditional for something that consequential: the trigger is now['**', '!beta'].PR coverage is untouched —
pull_requesthas no branch filter, so a PR into beta still runs this workflow in full. Only the direct push to beta is excluded.Removing the duplicate build would have silently removed gate coverage too, so three checks only
build.ymlhad are backfilled intobeta-build.yml:frame_vectors.h)DEV_TOOLS)Stated plainly: before this, a beta push — the one push that produces a published artefact — was the only path not checking
frame_vectors.hfor codegen drift, and never compiled the no-DEV_TOOLSbuild.Verification
Firestarter CIandPY32F071 firmwareboth pass onpushand onpull_request. The publish boundary is confirmed by observed step results, not by argument:Merge before #49 — until this lands, #49's branch still carries
pull_request: branches: [main], so the AVR gate cannot reach it and it is proven by ARM only.🤖 Generated with Claude Code