Repository navigation
fix(ci): the arm lane merges with an App token, so main's push lanes run (#2916) - #3003
Conversation
…run (#2916) Auto-merge is performed by GitHub as the identity that ARMED it. `auto-arm.yml` armed with `secrets.GITHUB_TOKEN`, so merges landed as `github-actions[bot]` — and a push created with GITHUB_TOKEN deliberately does not trigger workflow runs. main's HEAD therefore received no `push`-event run of anything: 8cc1cc6 merged by rbuergi Build and Test / Chart Gate / Hosting Operator 1962853 b3e6ae6 04d25ef dc6c5a4 merged by github-actions[bot] -> nothing No push run means no `Consolidate test results` on main's HEAD, which is the evidence main-cd gates publication on. It read `absent/none` and waited, every tick, for six hours across four commits: nothing was promoted and every self-updating install stayed on the previous image, with no red anywhere. The reconciler already separates "running" from "never ran". It cannot separate either from "the trigger was suppressed, so the check will never exist" — the skip-trapdoor shape one level down, where a suppressed trigger renders exactly like a slow one. Cure at the credential, not at the gate: teaching CD to accept the green-tree marker would let it publish while Chart Gate and Hosting Operator stayed silently dead. The arm now runs on a minted GitHub App installation token, requesting contents:write (the merge) and pull-requests:write (arming is the enablePullRequestAutoMerge mutation) so a missing grant fails at the mint, loudly, naming what to provision — with no fallback. ArmedMergeMustTriggerMainsPushLanesGuard fails the build if any workflow merges or arms a PR with GITHUB_TOKEN. It reads configuration, which is normally the weak shape, and says so: the outcome is unobservable from a PR run by construction, since what you would inspect is precisely what is missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SD7aiLSo59xng2TzU32xEa
There was a problem hiding this comment.
Pull request overview
This PR fixes a CI/CD outage mode where merges performed (or armed) with secrets.GITHUB_TOKEN suppress downstream push-triggered workflows, causing main’s push lanes (and therefore publishing) to never run.
Changes:
- Update
.github/workflows/auto-arm.ymlto mint and use a GitHub App installation token (with explicit permissions) rather thanGITHUB_TOKEN. - Add a guard test (
ArmedMergeMustTriggerMainsPushLanesGuard) that fails if any workflow merges/arms usingsecrets.GITHUB_TOKEN, and assertsauto-arm.ymlcontinues to mint and pass an installation token. - Document the “suppressed trigger” third state in the CD contract and add a What’s New entry describing the incident and fix.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
.github/workflows/auto-arm.yml |
Switch arming to a minted GitHub App token; add explicit preflight/assertions and better diagnostics. |
test/MeshWeaver.Documentation.Test/ArmedMergeMustTriggerMainsPushLanesGuard.cs |
Adds a guard test preventing reintroduction of GITHUB_TOKEN for merge/arm lanes and asserting the minted-token shape remains. |
src/MeshWeaver.Documentation/Data/Architecture/ContinuousDeliveryContract.md |
Documents the suppressed-trigger failure mode and why the fix must be credential-based. |
src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-02-updates-reach-your-installation-again.md |
Adds a user-facing What’s New entry describing impact and resolution. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Test Results (shard 3)586 tests 586 ✅ 51s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
Test Results (shard 5) 3 files 3 suites 1m 40s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)419 tests 419 ✅ 1m 34s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
Test Results (shard 4)1 685 tests 1 685 ✅ 1m 53s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
Test Results (shard 0)216 tests 216 ✅ 2m 5s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
Test Results (shard 2)531 tests 529 ✅ 1m 30s ⏱️ Results for commit b23b036. ♻️ This comment has been updated with latest results. |
… claim Both from the automatic review on #3003, and both right. `if: failure()` alone also fires when the secret-presence assert failed and the mint never ran — printing "could not mint an installation token" over a run whose real problem was the missing secret it had just named. Scoped to `steps.arm-token.outcome == 'failure'`. And the header claimed "no `if:` on any of the three steps below" while a fourth step now carries one. Reworded to the distinction that actually matters: no `if:` on a step that DECIDES anything; the one `if:` is on a step that only prints, after a failure, and can turn nothing green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SD7aiLSo59xng2TzU32xEa
Test Results 14 files 14 suites 9m 36s ⏱️ Results for commit b23b036. |
|
🚨 Heads-up from an unrelated PR: since this merged, Evidence that it is this change and not something pre-existing:
The lane's own failure step names the fix exactly, so nothing here is a mystery — it is just not done yet:
Not blocking anyone's merge — |
Fixes the mechanism behind #2916 — full RCA on the issue.
What was happening
Nothing had been published since
8cc1cc6e(15:13Z on 2026-09-01). Not because a job failed — because the image jobs never ran.GitHub performs an auto-merge as the identity that armed it.
auto-arm.yml(#2959, merged the same day) armed withsecrets.GITHUB_TOKEN, so every merge landed asgithub-actions[bot]— and a push created withGITHUB_TOKENdeliberately does not trigger workflow runs.push-event runs on that SHA8cc1cc6e15:131962853616:16github-actions[bot]b3e6ae6516:26github-actions[bot]04d25efa17:26github-actions[bot]dc6c5a4518:27github-actions[bot]No push run ⇒ no
Consolidate test resultson main's HEAD ⇒main-cd's readiness gate readsabsent/noneand waits — on every tick, forever. Six hours, four commits, every self-updating install frozen on the previous image, and every dashboard green throughout.The reconciler already distinguishes running from never ran. There is a third state it cannot see: the trigger was suppressed, so the check will never exist. That is the AGENTS.md skip-trapdoor rule one level down — a suppressed trigger renders exactly like a slow one, and absence is the one thing polling cannot age out of.
The fix — at the credential, not at the gate
Teaching CD to accept weaker evidence (the green-tree marker
refs/ci-green/<tree>/<epoch>) would let it publish whileChart GateandHosting Operatorstayed silently dead — one invisible hole traded for two. The merge simply has to be performed by an identity whose pushes behave normally.auto-arm.ymlnow:MESHWEAVER_APP_ID/MESHWEAVER_APP_PRIVATE_KEY— red, naming what to provision;permission-contents: write(the merge writes to the branch) andpermission-pull-requests: write(arming is theenablePullRequestAutoMergemutation), so a missing grant fails at the mint rather than downstream;GITHUB_TOKENis the outage itself.No
if:on any of the three steps. The one exemption stays on the job and remains a property of the event (draft, fork), never a question about whether a credential is set.🚨 Requires a grant before it can arm
The
meshweaver-cloudApp currently holds onlycontents: write+metadata: read:Grant
Pull requests: Read and writein the App's settings and accept the new permission on the org installation (a requested-but-unaccepted permission is not granted). Until then the arm job fails red naming exactly that — which is the intended shape, and does not block merging:Consolidate test resultsis the only required context, so PRs stay mergeable by hand meanwhile.auto-arm.ymlis identical in all seven fleet repos, so the one grant fixes the fleet; the file change should follow to the others.Guard
ArmedMergeMustTriggerMainsPushLanesGuardfails the build if any workflow merges or arms a PR withGITHUB_TOKEN, and if the arm lane stops naming a minted token (deleting theGH_TOKENline would recreate the failure by omission).It reads configuration, normally the weak shape — deliberately, and the file says so at length. The outcome ("did the merge start main's push lanes?") is unobservable from a pull-request run by construction: it can only be seen on main, after the merge, and what you would inspect is precisely what is missing. The credential is the only place a pre-merge check can stand, and it is causal rather than correlated — with
GITHUB_TOKENthe trigger is suppressed 100% of the time, by documented design.Verification
auto-arm.ymltoorigin/mainand ran the guard → 2/2 failed, namingauto-arm.yml; restored → passes.--filtered run is a false pass for ratchet guards):MeshWeaver.Documentation.Test168 passed, 0 failed,DocumentationLinkIntegrityTestpresent in the.trx.dotnet build -c Release -warnaserroron the touched project:0 Warning(s), 0 Error(s).Also conserved
Doc/Architecture/ContinuousDeliveryContractgains "The third state the reconciler cannot see — a SUPPRESSED trigger", beside the existing running-vs-never-ran paragraph it extends.Category: Fix) — an installation that looked stuck yesterday was not stuck; there was nothing newer to take.Still open, and it will re-fire the moment publication resumes
The last commit that actually reached the bake,
af817d9a(run 33522667934), failed on a genuine and unrelated defect — the images promoted and verified fine, only the bake leg went red:Two independent stoppages are stacked here. This PR fixes the trigger; the identity mismatch is not touched and will surface again once commits start reaching the bake.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SD7aiLSo59xng2TzU32xEa