Originally created by: adityaharishch
A macOS release build takes 72 minutes today. This rewrites the pipeline to
land at ~23 min cold / ~16 min warm, and fixes a correctness bug that has
already published a wrong version once.
Four independent problems, each verified before being fixed:
cargo tree against the real lockfile confirms the daemon's graph is fully
contained in the tray's - but they were built by two separate cargo
invocations, and features unify per-invocation. 54-96 shared crates resolved
with different feature sets, changing their -C metadata hash and forcing
214 crates to compile twice, cascading upward from the bottom of the graph.
The divergence is bidirectional (8 crates resolve wider on the daemon side:
tokio/macros, hyper/http1, serde_json/std, getrandom/std...), which is
what proves no amount of reordering fixes it. The fix is one invocation:
npx tauri build --no-bundle --target <triple> -- -p meridian --bin meridian
Note this is not "split them into separate jobs" - that was the first
proposal and it is wrong. Separate target dirs guarantee the daemon's 320
crates build twice and lose the 106 the tray currently reuses.
Run 29748827473 built both architectures and then died on The local branch
pre-main is behind the remote one. semantic-release's isBranchUpToDate check
requires the branch HEAD, which is structurally incompatible with building a
pinned SHA; upstream declined to make it configurable (semantic-release#1849).
So semantic-release no longer runs during the build. release-prepare.yml
runs it at branch HEAD in ~60s, pushes the tag and a draft release;
release-build.yml triggers on the tag and only ever attaches assets.
Related: tauri-action's built-in latest.json upload is a lockless
read-modify-write against one shared asset, once per matrix job - two runners
finishing in the same window silently drop each other's platform key. Its own
source concedes it. scripts/compose-updater-manifest.py composes the manifest
once, after every runner is done, and refuses to emit anything it cannot
first prove is complete.
Two macos matrix jobs (aarch64 / x86_64) rather than one universal build.
The win is not raw compile time - it is that the two notarization waits
overlap instead of queueing behind a lipo join. Also halves DMG size and
deletes the join job. Existing installs migrate transparently: the updater
resolves its platform key from the running CPU, not from the manifest.
Keys were fragmented three ways, release.yml had a bare rust-cache, and
restore falls back only to the default branch (so main could never read
pre-main's). 8.53 GB of a 10 GB quota, actively evicting.
windows-portability ran cargo check --all-targets and cargo test overci-linux/ci-windows/ci-macos) instead of job-id~/.cargo/registry/src rather than re-unpacking ~1000 crateCARGO_INCREMENTAL=0 - incremental state is only ever written on CI, nevermain before it isNothing here has ever executed. Static validation only (actionlint,
py_compile, an end-to-end composer test with hand-made fragments). I would
put first-try success at maybe 30-40%.
Two things a reviewer should know:
Meridian.dmg, so the assets areMeridian-aarch64.dmg / Meridian-x64.dmg. The download page needs twomain.release.yml is deletedInstalled apps cannot be bricked by a bad build: minisign signature
verification happens before an update is applied.
Suggested path: merge to pre-main, cut two clean [staging-release]
builds end to end, and only then consider main.
workflow_dispatch release-prepare.yml with dry_run: true[staging-release] run, twice, verifying both DMGs, both platformlatest.json, and an actual staging auto-updatesecrets.GH_TOKEN can push tags (see settings note below)
secrets.GH_TOKENbeing a PAT is load-bearing, not a nicety: GitHub does
not re-trigger workflows for refs pushed with the defaultGITHUB_TOKEN.
A tag pushed with it would succeed and then nothing would build.
Originally posted by: coderabbitai[bot]
β¨ Finishing Touches
π§ͺ Generate unit tests (beta)
- [ ] Create PR with unit tests - [ ] Commit unit tests in branch `ci/release-pipeline-clean`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.
β€οΈ Share
- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)Comment
@coderabbitai helpto get the list of available commands.Originally posted by: adityaharishch
Update β scoped to staging only, production left untouched
Per the ask ("do not change the main pipeline, first test only the staging pipeline if it's become faster"), this no longer touches production:
release.yml(production,main) is byte-for-bytepre-main's current state β including the recent [#499] parallelization. Restored, not deleted.release-prepare.ymltriggers onpre-mainonly (drops the[release]marker);release-build.yml's tag filter isv*-staging*, notv*β so a production tag (v1.73.0) fires only the oldrelease.ymland the two pipelines can never collide.release-staging.ymlstays deleted β the new pipeline replaces it.Migration path once staging proves faster: widen the build tag filter to
v*and re-addmainto prepare β one change, both in the same commit.Also: merged latest
pre-mainin, so this is now conflict-free. And #506 (PostHog) was closed β [#505] merged the identical fix directly.How to test staging (nothing production touched)
pre-main.[staging-release]commit topre-main(orworkflow_dispatchβrelease-preparewithdry_run: truefirst).release-preparetagv*-staging.Nin ~60s, thenrelease-buildfan out per-arch. Time it against a recent staging run.latest.json, and a real staging auto-update.Production keeps shipping through
release.ymlthe entire time.Related
Tickets:
#499Tickets:
#505Originally posted by: Akarsh-Hegde
Review
Read the full diff plus the actual
release-prepare.yml/release-build.yml/compose-updater-manifest.py/package-updater.sh/bootstrap.sh/ bothCargo.tomls /tray/package.jsonand the deletedrelease.yml+release-staging.yml. The architecture is right β split prepare (branch-HEAD, ~60s) from tag-triggered build (immutable ref) is the correct fix for theisBranchUpToDatevs pinned-SHA conflict, draft-first + compose-once + completeness-assert genuinely removes the tauri-action manifest race, and the reasoning is unusually well documented. But there are three things I'd treat as blockers, and they line up with your own "nothing here has ever executed" caveat.Blockers
1. The single cargo invocation almost certainly does not build the tray at all.
release-build.ymlβnpx tauri build --no-bundle --target X -- -p meridian --bin meridian, run fromworking-directory: tray.meridian(daemon,.) andmeridian-tray(tray/src-tauri) are in one shared workspace (rootCargo.tomlmembers) β I confirmed this. Tauri appends everything after--to its cargo call and does not itself inject-p meridian-tray. So the effective command iscargo build β¦ -p meridian --bin meridian, and-p meridianselects only the daemon package β the tray is excluded, and--bin meridianfurther restricts to the daemon bin. The subsequenttauri bundlestep does zero compilation (your own comment says so), so it then has nomeridian-traybinary to package. Under every interpretation this command builds the daemon or errors on--featuresmismatch; none of them produce the tray. Your "must print nothing"uniq -dcheck only catches double compiles, not a missing tray. Concrete test before anything else: fromtray/src-tauri, runcargo build -p meridian --bin meridianand check whethertarget/*/release/meridian-trayexists. If it doesn't, every build job is broken β this blocks staging too, not just main. To build both in one invocation you likely want--bin meridian-tray --bin meridian(both bins, one workspace resolve) rather than-p meridian --bin meridian.2. Stable tags will push but never build β
[skip ci]on the release commit suppresses the tag trigger..releaserc.jsonkeeps@semantic-release/gitwith"message": "chore(release): ${version} [skip ci]β¦". semantic-release tags that commit;release-build.ymlfires onpush: tags: [v*]; GitHub evaluates skip directives against the tagged commit's message on a tag push and skips the workflow. Net on stable: tag appears, nothing builds. Staging is unaffected (.releaserc.staging.jsonhas no@semantic-release/git, so no[skip ci]commit and the tag points at the real HEAD). Verify on a throwaway tag; if confirmed, move[skip ci]out of the release commit message or trigger the build offrelease createdinstead of the tag push. Not blocking the pre-main path, but a hard blocker before main.3.
bootstrap.shβ the primarycurl | bashmacOS installer β will 404 on stable.scripts/bootstrap.sh:30downloadsreleases/latest/download/Meridian.dmg. Per-arch builds only ever produceMeridian-aarch64.dmg/Meridian-x64.dmg(package-updater.shemits the plainMeridian.dmgname only in theuniversal-apple-darwinbranch, which no longer runs).README.md:72andSETUP.md:13point at the sameMeridian.dmg. This is a broken install path + broken docs, not just "the download page needs two links" β the installer script needs an arch-detect (uname -mβ aarch64/x64) before this reaches main.bootstrap.ps1is fine.Major
4. Release builds never warm from cache β the branch
save-ifclauses are dead code. Both macOS (shared-key: macos-release-${target}) and Windows jobs gatesave-ifonrefs/heads/main || refs/heads/pre-main || startsWith(github.ref,'refs/tags/v'). Butrelease-build.ymlonly ever runs on av*tag (or dispatch), so the two branch clauses are never true β only the tag clause fires, and a cache saved underrefs/tags/vXis scoped to that ref and unreadable by the next release'srefs/tags/vY. Nothing writes these shared-keys on a branch ref, andci.yml's keys are deliberately disjoint (ci-macos, β¦), so release builds always cold-miss. That defeats the headline "~16 min warm" goal for the path that matters most. To actually warm, these need to be saved on a branch ref the next tag build can inherit (or reuse theci-*lineage).Minor
add-job-id-key: falseis not a documentedSwatinem/rust-cache@v2input (used throughoutci.yml+release-build.yml). Unknownwith:keys are silently ignored; the cross-job sharing actually works becauseshared-keyalone replaces the job-id component. Harmless but misleading to a future reader β drop it or confirm against the action'saction.yml.mirror-updater-channel.shruns inpublishonubuntu-latest, which built nothing, so its DMG glob matches nothing and onlylatest.jsonis mirrored toupdater-staging. Auto-update still works (manifest URLs point at the versioned release); thereleases/download/updater-staging/Meridian*.dmglink testers used is a silent regression frommirror-staging-release.sh.create-icons.shno longer runs (old stableprepareCmdran it; nothing does now). Fine if the committedicon.icnsis current β given the icon-clobber history, confirm the committed icons are the intended spirograph..releaserc.staging.json's@semantic-release/githubhasdraftRelease: true, so semantic-release creates the draft;release-build'spreparejob alsogh release create --drafts and reuses it. Fine in the normal order; ifprepareever wins, semantic-release 422s. Low probability, worth a guard-comment.Nit
cp target/release/meridian.exe β¦meridian.exe.stub || trueβ copies the real daemon to a.stubname and does nothing useful beforetauri bundle. Harmless leftover.Verified OK / correcting the record
MERIDIAN_RUNTIME_MANIFEST_URLon staging) is a non-issue β I grepped the whole tree and it has no consumer (only the oldrelease-staging.ymland the localbuild:stagingscript set it; noenv!/option_env!reads it). It's vestigial after the MLX-runtime retirement, so dropping it changes nothing.MERIDIAN_CHANNEL(which is read viaoption_env!inversion.rs) is set correctly.compose-updater-manifest.pyis solid: refuses partial/empty/version-mismatched/duplicate fragments and requires all three platform keys, so no partial manifest can publish. Withfail-fast: false, a failed matrix leg failsmacosandpublishis skipped (needs-failed) β nothing partial ships. Manifest-uploaded-last ordering is correct.updater-<triple>.jsonwritten / uploaded / consumed); Windows emits bothwindows-x86_64and-nsiskeys.import-apple-certexists; daemon-sign β bundle β notarize-DMG order is preserved for both arches.tauri.staging.conf.jsonexists.Bottom line: the design is sound and I'd take it over the old pipeline, but [#1] needs an actual local
cargo buildcheck before this runs anywhere, and [#2]/#3 must be fixed before it reachesmain. Your suggested path (merge to pre-main, cut two[staging-release]builds end to end first) is the right call β just note [#1] would break those staging builds too, so verify it first.Related
Tickets:
#1Tickets:
#2Originally posted by: adityaharishch
CI restructured into a native-runner matrix (+ real Intel, post-merge)
The three hand-written Rust jobs are now one matrix that fans out per native runner, plus the fixes discussed:
changesgate βfmt(once) Β· Linux Β· Windows Β· macOS Apple Silicon Β· UImacos-13What changed and why:
macos-13runner (arm can't execute x86_64 binaries, so the old arm "Intel slice" was only a compile-check). But β measured live β amacos-13matrix entry sat queued 25+ min without ever getting a runner (GitHub is retiring Intel runners). Putting that on every PR would make Intel the long pole and kill the speed win. So Intel runs post-merge only (pushto pre-main/main), where a long queue is fine; PRs stay fast. Confirmed: the PR run above skips Intel.dorny/paths-filtergate β a docs/UI-only PR skips all three compile jobs (safe: pre-main is unprotected, so a skipped job just doesn't run).fmtruns once off the matrix instead of 3Γ.checkthentestβ a wasted full build) is in.Each matrix entry keeps its exact prior per-platform commands (the workspace is asymmetric β the tray is macOS-only), so Linux/Windows behaviour is byte-for-byte preserved; only Intel (checkβtest) and arm (added daemon) gained coverage.
Originally posted by: adityaharishch
CI final shape β macOS Apple Silicon only (supersedes the matrix comment above)
Simplified further: Meridian ships macOS arm, so that's the only Rust gate. Windows, Intel (macos-13), and Linux jobs are removed.
Jobs now:
Caching saves on pre-main (a PR restores its base branch's cache), so PRs go warm as soon as the first pre-main run seeds the key β no wait for a release to
main. One cold run when this lands, ~4-6 min warm after.Trade-off, stated plainly: dropping Windows + Intel means those platforms are no longer built/tested in CI. Intel still ships in the universal binary but is unverified here; a Windows-breaking change won't be caught. Accepted β macOS arm is the shipped target.
Ticket changed by: adityaharishch