Skip to content

ci: run gates on every branch, one publisher per branch - #50

Merged
henols merged 3 commits into
betafrom
ci/build-all-branches-publish-main-beta
Aug 7, 2026
Merged

ci: run gates on every branch, one publisher per branch#50
henols merged 3 commits into
betafrom
ci/build-all-branches-publish-main-beta

Conversation

@henols

@henols henols commented Aug 6, 2026

Copy link
Copy Markdown
Owner

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 main only, the ARM gate on beta only. A break surfaced at merge time rather than when it was written — and a PR targeting beta got no AVR CI at all, which is exactly what happened to #49.

Resulting shape

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, never publishes

One publisher per branch. No path publishes twice.

99fa244 — open the triggers, make publishing explicit

Three steps in build.yml mutate the repo or publish, and their safety was implied entirely by the main-only trigger. Each now carries if: github.event_name == 'push' && github.ref == 'refs/heads/main'.

This also closes a latent duplicate-publish bug that predates this PR. On beta today, build.yml triggers on pull_request: [main] with all three steps ungated — so every PR into main would bump the version, auto-commit onto the PR branch, and cut a make_latest release. Merging then republishes the same version derived from the same include/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 skipped on a feature-branch push, while the build still runs.

branches: ['**'] rather than a bare push: — a bare trigger also fires on tag pushes, and this project pushes tags across three repos at milestone close expecting zero CI.

A concurrency group per workflow supersedes stale runs, with cancel-in-progress explicitly false on main and beta so two rapid pushes to a publishing branch queue instead of racing.

665ba99fetch-depth: 0, caught by the change itself

Opening the trigger made pytest tests/ -v run against beta-derived branches for the first time. It failed immediately:

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 walk git history, and actions/checkout@v4 defaults to a shallow depth-1 clone. Invisible before because build.yml ran on main only, and main lags beta far enough not to carry those test files — the step passed for want of anything to run. beta-build.yml has carried fetch-depth: 0 all along.

25235c4 — keep the stable publisher off beta, backfill beta's gates

['**'] made build.yml fire on beta pushes alongside beta-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 — one if: edit away from doing so. Structural exclusion beats a conditional for something that consequential: the trigger is now ['**', '!beta'].

PR coverage is untouched — pull_request has 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.yml had are backfilled into beta-build.yml:

  • Vector catalog validity check
  • Codegen drift gate (frame_vectors.h)
  • Native unit tests (no 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.h for codegen drift, and never compiled the no-DEV_TOOLS build.

Verification

Firestarter CI and PY32F071 firmware both pass on push and on pull_request. The publish boundary is confirmed by observed step results, not by argument:

14. Run update_version.py tests ....... success   <- fetch-depth fix
15. Generate release version .......... skipped
16. git-auto-commit-action ............ skipped
17. Build PlatformIO Project .......... success   <- deliberately outside the boundary
18. Release ........................... skipped

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

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>
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
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>
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
@henols henols closed this Aug 6, 2026
@henols henols reopened this Aug 6, 2026
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>
@henols henols changed the title ci: build on every branch, publish only from main ci: run gates on every branch, one publisher per branch Aug 7, 2026
@henols
henols merged commit 91aee89 into beta Aug 7, 2026
4 checks passed
@henols
henols deleted the ci/build-all-branches-publish-main-beta branch August 7, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant