Uh oh!
There was an error while loading. Please reload this page.
CI: fan out mise tool setup to eliminate redundant SPM builds - #314
Conversation
Both MistKit.yml and MistDemo.yml lint jobs invoked jdx/mise-action@v4 with cache: true against the same root mise.toml, computing identical cache keys and racing each other on every push. The loser hit "Failed to save: Unable to reserve cache with key ..., another job may be creating this cache" and got nothing, forcing a clean ~9 minute SPM rebuild of swift-format, swift-openapi-generator, and periphery. The default mise-action cache also lacked restore-keys, so any mise.toml bump produced a clean rebuild instead of an incremental one. Fix: extract a reusable setup-tools.yml that owns the cache via actions/cache@v4 with restore-keys fallback. Lint jobs now depend on setup-tools and do read-only actions/cache/restore@v4 with fail-on-cache-miss: true (so a broken hand-off fails loudly), then run mise-action with install: false / cache: false so it only configures PATH/shims against tools already on disk. Cold-cache cost is paid once per workflow run instead of once per project, and a mise.toml version bump becomes an incremental SPM rebuild rather than a clean one.
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Code Review: CI Fan-Out for mise Tool SetupOverviewThis PR fixes a real and annoying problem: two lint jobs (MistKit + MistDemo) computed identical mise cache keys and raced each other, with the loser paying a full ~9-minute cold SPM rebuild. The fix — a shared The PR also carries significant changes to the example subprojects (BushelCloud, CelestraCloud, MistDemo), which appear to be independent subrepo syncs bundled in the same branch. Core CI Changes (.github/workflows/)What works well:
Issues and suggestions: 1.
# MistDemo.yml paths — consider adding:
- '.github/workflows/setup-tools.yml'2.
3. Reusable workflows inherit caller permissions, but declaring jobs:
setup-tools:
permissions:
contents: readruns-on: ubuntu-latest4. The lint job has 5. Cache key uses Both Example Subproject ChangesThe BushelCloud and CelestraCloud lint jobs still use The CelestraCloud Swift changes (new SummaryThe core fix is correct and well-reasoned. Three actionable items before merging:
The pagination gap in Review generated by Claude Code |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@## v1.0.0-beta.1 #314 +/- ##
==============================================
Coverage 67.16% 67.17% ==============================================
Files 495 495 Lines 13869 13869 ==============================================
+ Hits 9315 9316 +1 + Misses 4554 4553 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Adds a concurrency group on setup-tools.yml keyed on the ref so that when both MistKit.yml and MistDemo.yml trigger on the same push, their setup-tools invocations run sequentially. On a cold cache (e.g. after a mise.toml bump), the first caller rebuilds and saves; the second waits, then hits the cache and skips the install step. Without this, both could miss simultaneously, both rebuild, and race on cache save — the original problem we were trying to eliminate. cancel-in-progress: false so the queued caller actually runs instead of being cancelled.
Code Review: CI Fan-Out for mise Tool SetupOverviewThis PR correctly diagnoses and fixes a cache reservation race between Correctness
Potential Issues1. The concurrency:
group: setup-tools-${{ github.head_ref || github.ref }}cancel-in-progress: falseWhen called via 2. Cache path is platform-specific
3. No # Suggest adding:permissions:
contents: readGitHub Actions jobs default to broad permissions in some org configurations. Explicitly scoping to 4. The This will match any prior Code Quality
Suggestions
Test CoverageThe test plan in the PR description is appropriate. No Swift source changes are made, so no unit/integration test changes are needed. CI itself is the test. SummaryApprove with minor suggestions. The core approach is correct and well-reasoned. The two actionable items are: (1) add a |
Code Review — PR #314: CI cache optimization (fan out mise tool setup)OverviewThis PR addresses a real, well-diagnosed problem: two lint jobs sharing the same cache key racing each other on every push. The fix — a shared Code Quality & CorrectnessPositives:
Suggestions:
Potential Risks
SecurityNo concerns. Cache paths are scoped to tool installations only, not source code or credentials. SummaryWell-reasoned fix for a concrete CI reliability problem. The only actionable item is adding |
The reusable workflow approach failed in practice: MistDemo's lint job hit "Failed to restore cache entry" with fail-on-cache-miss in the same workflow run where setup-tools reported a 10-second cache hit on the same key. Caches written by actions/cache from inside a workflow_call reusable workflow are scoped to the reusable workflow's path, while actions/cache/restore from the caller looks in the caller's scope — they don't see each other. Switch to a composite action at .github/actions/setup-tools so cache operations run inline in the caller's job and inherit the caller's scope. Each lint job now does its own restore + (install on miss) + save. Branch-scoped caches mean the first job to save makes the entry visible to the second. To preserve the cross-workflow serialization that prevents a cold- cache race when MistKit and MistDemo lint trigger on the same push, add a shared job-level concurrency group (lint-tools-<ref>, cancel-in-progress: false). First job rebuilds and saves; the second queues, runs, hits cache, and is fast.
Code Review: PR #314 — CI: Composite Action for mise Tool CacheOverviewA targeted, well-diagnosed CI fix. The switch from StrengthsComposite action vs. reusable workflow Conditional install
Issues
NitThe PR description says lint jobs use SummaryCorrect, minimal change that solves a real CI problem. No issues warrant blocking merge. Reviewed with Claude Code |
Code Review: PR #314 — CI: fan out mise tool setupOverviewAddresses a real cache-race bug: both workflow lint jobs computed identical mise cache keys and raced each other on every push, with the loser forced into a ~9-minute clean SPM rebuild. The fix serializes cache ownership via Strengths
Issues and Suggestions1. Concurrency group key uses On pushes to the default branch (no PR), 2. The action uses 3. No test plan items are checked The test plan lists four scenarios but none are checked:
These can only be verified by actually running CI — worth a note that they'll be checked on merge, or checking them off if CI has already been run against this branch. SummaryCorrect fix for a real problem. The composite action design is well-reasoned and the 🤖 Reviewed with Claude Code |
Code Review — PR #314: CI tool setup fan-outOverviewClean fix for a real CI pain point. The composite action approach (rather than a reusable workflow) is the right call — reusable workflows create their own cache scope that can't be shared back to the caller job, which would reintroduce the hand-off problem. The Issues🟡 |
Code Review — PR #314: CI tool setup fan-outOverviewA focused CI fix: two lint jobs were racing to write the same mise cache key, causing one to fail and rebuild all SPM-backed tools (~9 minutes) from scratch. The fix is a composite action ( Strengths
Potential IssuesLow — Both lint jobs share However: if a push triggers both workflows at the same time, the second job does not start at all until the first lint job completes (not just the cache-write step). Depending on how long the lint step itself runs, this could add noticeable serial latency for PRs that touch both projects. It would be slightly more efficient to have the Low — Cache path may differ between macOS and Linux runners The cache path Nit — Action description says "reusable workflows scope caches separately" The composite action's description comment ( Correctness
SummaryClean, well-reasoned fix with no functional impact. The composite action pattern is the correct choice for the cache scoping constraint, and the 🤖 Generated with Claude Code |
Code Review — PR #314: CI mise tool cache fan-outTargeted review of the CI change. The diagnosis is accurate and the fix is correct. DesignComposite action over reusable workflow — correct choice. Cache key + The prefix fallback means a
Issues1. Serialised lint jobs may double wall-clock timeBoth concurrency:
group: lint-tools-${{ github.head_ref || github.ref }}cancel-in-progress: falseGitHub Actions concurrency groups are global across all workflows in the repo. On a PR that touches both projects, the two lint jobs run sequentially instead of in parallel. The intent is to avoid the cache race, but once the cache is warm (second push onward), the jobs no longer need to be serialised — only the cold-cache path needs it. This is a trade-off worth calling out: warm-cache CI takes twice as long as it could. An alternative is to keep 2. |
Uh oh!
There was an error while loading. Please reload this page.
Diagnosis
Both
.github/workflows/MistKit.ymland.github/workflows/MistDemo.ymllint jobs invokedjdx/mise-action@v4withcache: trueagainst the same rootmise.toml. They computed identical cache keys and raced each other on every push — the loser hit:…and got nothing for it, forcing a clean ~9-minute SPM rebuild of
swift-format,swift-openapi-generator, andperiphery(allspm:backend tools). This was happening on essentially every PR that touched both projects.A second, separate problem: the default
jdx/mise-actioncache configuration has norestore-keysfallback, so anymise.tomlversion bump (even a patch on a single tool) produced a clean rebuild from scratch instead of an incremental SPM rebuild against the previous tool tree.Fix
Fan out from a single setup job that owns the cache, and have both lint workflows consume the populated cache read-only.
.github/workflows/setup-tools.yml(triggered viaworkflow_call) owns cache lifecycle:actions/cache@v4with path~/.local/share/mise/installs, keymise-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('mise.toml') }}, plus arestore-keysfallback on themise-v2-${{ runner.os }}-${{ runner.arch }}-prefix.jdx/mise-action@v4withcache: falseonly on cache miss (steps.<id>.outputs.cache-hit != 'true'), so we own the cache lifecycle and avoid the reservation race.MistKit.ymlandMistDemo.yml:setup-toolsjob that calls./.github/workflows/setup-tools.yml.setup-toolsto each lint job'sneeds:list.jdx/mise-action@v4invocation withactions/cache/restore@v4(read-only, same key/path as setup-tools,fail-on-cache-miss: trueso a broken hand-off fails loudly instead of silently rebuilding) followed byjdx/mise-action@v4withinstall: falseandcache: false(mise just configures PATH/shims against tools already on disk).mise.tomlis unchanged. No tools were swapped to other backends (peripherystays onspm:because there's no Linux binary release inubi:). No scripts underScripts/were touched.Expected impact
mise.tomlversion bumps become incremental SPM rebuilds rather than clean rebuilds, sincerestore-keyswill pick up the previous tool tree.Test plan
setup-toolspopulates the cache; both lint jobs do a read-only restore and pass.mise.tomlchange): cache is hit onsetup-tools(no install step runs), lint jobs restore quickly.mise.toml:setup-toolsmisses on exact key,restore-keyspicks up prior tree, mise-action does an incremental SPM rebuild rather than a full clean one.setup-toolsjob ever fails to populate.Generated by Claude Code
Perform an AI-assisted review on