ci(mirror): chain the S3 mirror off Release instead of a manual dispatch - #15
Merged
Conversation
The mirror was `workflow_dispatch`-only, so a tag push published to GitHub and npm but stopped short of download.bugsee.com unless someone remembered to trigger it. v0.7.4 is what that looks like in practice: released 2026-08-11, never mirrored, so the CDN served 0.7.3 for six weeks and `bugsee-cli update` — which reads `cli/v<major>.x/version.txt` — never offered it. Nothing failed; the release just quietly stopped halfway. The old header explained the manual trigger by noting that GitHub suppresses downstream events for activity initiated by the default GITHUB_TOKEN, which is what dist's release.yml publishes the Release with. That is true of `release:`/`push:` but NOT of `workflow_run`, which fires on another WORKFLOW completing and is not suppressed — npm-publish.yml has been chaining off Release that way all along. Same mechanism and the same guards are adopted here: a SUCCESSFUL, tag-push-triggered Release whose ref matches `vX.Y.Z`. `workflow_dispatch` is kept for backfills. Both paths converge on a `resolve` job that normalizes the tag; a hand-typed tag that isn't `vX.Y.Z` now fails loudly rather than "succeeding" without mirroring, while a non-version tag on the automated path is skipped quietly. Splitting resolve into its own job means the credentialed job needs no per-step `if:` guards, so a step added later cannot silently run against a tag we meant to skip. Also makes the `cli/latest/` artifact sync ADVANCE-ONLY, matching the version pointers. Previously it was overwritten unconditionally, so mirroring an older tag after a newer one would leave `cli/latest/version.txt` pointing at the new version with the OLD binaries beside it — a silent mismatch. Sequencing the v0.7.4-then-v0.7.5 backfill by hand was what kept that from happening; it is now a property of the workflow. The concurrency group is global rather than per-tag for the same reason: `cli/latest/` is one shared mutable prefix and two mirrors must never overlap. OIDC is unaffected: a `workflow_run` run executes from the default branch, so `github.ref` is `refs/heads/main` — exactly as for a dispatch from main — and the role's trust policy sees an unchanged subject. Verified: actionlint clean; the advance-only guard and the tag-resolution logic each unit-tested against their edge cases (version regression, absent pointer, re-mirror, 0.10.0-vs-0.9.0 numeric ordering, `vnext`, typo'd manual input). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Makes
download.bugsee.compart of a release automatically, closing the gapthat left v0.7.4 unmirrored for six weeks.
The problem
mirror-to-s3.ymlwasworkflow_dispatch-only, so a tag push published toGitHub and npm but stopped short of the CDN unless someone remembered to trigger
it. v0.7.4 is what that looks like in practice: released 2026-08-11, never
mirrored — the CDN served 0.7.3 until yesterday, and
bugsee-cli update(whichreads
cli/v<major>.x/version.txt, seesrc/cli/update.rs:25) never offered it.Nothing failed. The release just quietly stopped halfway.
Why this is safe to automate now
The old header justified the manual trigger: GitHub suppresses downstream events
for activity initiated by the default
GITHUB_TOKEN, and dist'srelease.ymlpublishes the Release with exactly that token.
That's true of
release:/push:— but not ofworkflow_run, which fireson another workflow completing and is not suppressed.
npm-publish.ymlhasbeen chaining off Release this way all along and did so successfully for v0.7.5.
Same mechanism and the same guards are adopted here, so the two post-release
workflows now work identically.
OIDC is unaffected. A
workflow_runrun executes from the default branch, sogithub.refisrefs/heads/main— exactly as for aworkflow_dispatchfrommain, which is how every mirror to date has run. The role's trust policy sees an
unchanged subject.
What changed
workflow_runon["Release"], guarded to a SUCCESSFUL, tag-pushtriggered Release whose ref matches
vX.Y.Z(PR-mode and failed releases arefiltered out).
workflow_dispatchis kept for backfills.resolvejob both paths converge on. A hand-typed tag that isn'tvX.Y.Znow fails loudly rather than "succeeding" without mirroring; anon-version tag on the automated path is skipped quietly. It's a separate job
so the credentialed job needs no per-step
if:guards — a step added latercan't silently run against a tag we meant to skip.
cli/latest/artifact sync is now ADVANCE-ONLY, matching the versionpointers. It used to be overwritten unconditionally, so mirroring an older tag
after a newer one would leave
cli/latest/version.txtpointing at the newversion with the old binaries beside it. Hand-sequencing v0.7.4 before
v0.7.5 yesterday is what prevented that; it's now a property of the workflow.
cli/latest/is one shared mutable prefix and two mirrors must never overlap.
Validation
actionlintclean on this file and every other hand-written workflow. (Itreports shellcheck style nits in
release.yml, which is cargo-dist generatedand deliberately untouched.)
Advance-only guard — 9/9:
Tag resolution — 8/8, including
vnext(passesstartsWith('v')but iscorrectly skipped, not failed) and a typo'd manual
0.7.4without thev(fails loudly).
dist plan,cargo test --all-targets,cargo fmtall still pass.Remaining verification
The
workflow_runpath can only be fully exercised by a real release. Oncemerged, the
workflow_dispatchpath can be smoke-tested immediately byre-mirroring
v0.7.5— idempotent, since the guard treats an equal version as aharmless overwrite of identical bytes.
Not included
docs/unity-il2cpp-linenumber-mappings.mdhas unrelated in-progress edits in theworking tree; deliberately left out.
🤖 Generated with Claude Code