Skip to content

ci: key reload-build caches on the commit and fall back across branches - #14099

Merged
teamleaderleo merged 3 commits into
mainfrom
ci/reload-build-stable-cache-key
Sep 24, 2026
Merged

teamleaderleo merged 3 commits into
mainfrom
ci/reload-build-stable-cache-key

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

reload-build.yml rebuilt cold on dispatches that should have been warm. Run 35938367902 (ref=9a0702e0f5) restored nothing and spent 753 s in the build step (derived_data_cache_status: miss, empty matched_key). A second dispatch for the same branch with a full SHA, 35941854226, missed the same way.

Cause. The DerivedData key embedded a slug of the raw ref input. A short SHA, a full SHA and a branch name for one commit therefore produced three different keys, and each of reload-cloud's per-call branches got a key of its own. The only cross-branch restore-key was <prefix>main-, which a run writes only when dispatched with ref=main. Nothing does that, so the fallback never matched.

Now.

  • The DerivedData save key is <prefix>commit-<resolved sha>-run-…. Every spelling of a commit keys identically.
  • The last restore-key is the Xcode + runner OS/arch + checkout-path prefix on its own. It restores the newest entry from any commit or branch built with the same toolchain and path, including existing v3 entries.
  • Adopting a foreign entry is safe. The workflow already sets each tracked source's mtime from its blob id, so any file that differs from the cached build recompiles. A cached build that fails already clears both caches and retries cold.
  • SPM gets the same kind of fallback, <spm prefix> with no Package.resolved hash. A package bump no longer throws away every other checkout; fallback hits already go through the sanitizer.
  • actions/cache scopes entries to the dispatch ref. A run dispatched on an ephemeral branch still restores default-branch entries, but nothing else can read what it saves. Of the last 60 runs, 49 were dispatched on non-default branches (for example tmp/ios-buildfix ×24 and reload-blacksmith/*). The workflow now emits a notice when that happens, and the header says to dispatch on main with -f ref=<branch>. The recent reload-cloud dispatches already do this.
  • timings.json: cache_branch_key is replaced by cache_scope_ref, the ref whose cache scope the run saves into.

Validation.

  • tests/test_ci_reload_build_cache_keys.py is new and registered as linux-guard. It runs in the app-host-cache group in ci-guards.yml. It runs the workflow's real metadata script against a scratch repository and resolves the restore-keys the workflow passes to actions/cache. It asserts that the empty ref, short SHA, full SHA, branch and refs/heads/<branch> all give one key. It also asserts that a later commit whose Package.resolved changed restores an earlier entry for both caches under any ref text.
  • Commit 1 (test only) fails locally with five distinct keys; commit 2 passes.
  • actionlint is clean on reload-build.yml and ci-guards.yml.
  • I ran every tests/test_*.py and tests/test_*.sh on this branch and on an origin/main worktree. The results are identical except for the new test. test_ci_app_host_xcodebuild_retry.sh fails in both trees when rerun alone: it relies on a 0.1 s idle timeout and is load-sensitive.
  • Live A/B: a dispatch of this branch is running now. The results will go in a comment.

Same footgun elsewhere: none with ref-text keys. Every other cache key uses a resolved SHA or a content hash. The related gaps are listed below, with none changed here:

  • ci-macos-compat.yml:132-135: the DerivedData key has no restore-keys, and saves land in the dispatch branch's scope. A restore-key alone would not help, because this workflow does not normalize mtimes, so a restored DerivedData recompiles everything anyway.
  • ci-macos.yml:454-457 (also :2464, :3271): the ghosttykit-sentry-off-v1-<sha> restore has no main-scoped writer. Only dispatch-only ci-macos-compat.yml saves that key, so it misses and falls through to download-prebuilt-ghosttykit.sh. It costs time, not correctness.
  • test-ios.yml:443-457: the only writer is gated on inputs.seed_cache on main, and no caller passes it. iOS DerivedData is never cached.
  • reload-build.yml with platform=ios: every cache step is macOS-only, so iOS reloads are always cold.
  • perf-activation.yml:210 and tmux-corpus.yml:56-76: dispatch-only, with no DerivedData cache and caches saved only into the dispatch scope.
  • test-e2e.yml:46: the concurrency group uses raw inputs.ref, so a short SHA and a full SHA of one commit do not cancel each other. This is not a cache issue.

Related: #14081 seeds per-main-push DerivedData to R2 for PR admission. It is a different build (admission products, a different path contract), so reload-build cannot adopt it as is. Once it lands, pointing this workflow's fallback at an R2 seed with the same contract would remove the dependence on a recent main-scoped dispatch.

— Rivetmoss g1 🦉

🤖 Generated with Claude Code


Summary by cubic

Fixes reload-build cache misses by keying DerivedData and SPM caches on the resolved commit SHA instead of the raw ref text, so short SHAs, full SHAs, branch names, and refs/heads/<branch> all hit the same entry. Adds a generic Xcode/runner/workspace prefix fallback so any commit or branch built with the same toolchain and checkout path restores the newest cache.

  • A Package.resolved bump no longer discards other package checkouts; SPM fallback hits go through the sanitizer.
  • Dispatches on a non-default branch now emit a notice that their saves are visible only to later dispatches on that ref.
  • timings.json records cache_scope_ref in place of the retired cache_branch_key.
  • A new guard test runs the workflow's real cache-metadata script in a scratch repo, substituting each ref spelling into the step's own env block, and asserts key stability, cross-ref restore behavior, and the off-default notice.

Written for commit 5ff0959. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Build Improvements
    • macOS reload builds can reuse compatible cached dependencies and build data across commits and branch names, potentially reducing build times.
    • Cache matching now accounts for the Xcode version, runner environment, and workspace.
  • Documentation
    • Clarified how to dispatch reload builds and how the dispatch branch affects cache availability.

teamleaderleo and others added 2 commits September 23, 2026 18:45
Run 35938367902 dispatched reload-build with a short SHA and rebuilt cold in
753 seconds; a follow-up dispatch with the full SHA on the same branch also
restored nothing. The DerivedData key embeds the raw `ref` input, and the only
cross-branch fallback is a `main-` prefix that no dispatch saves.

This guard runs the workflow's real cache-metadata script and asserts that
every spelling of one commit keys identically, and that a later commit under
any ref text restores an earlier entry. It fails on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reload-build derived its DerivedData key from the raw `ref` input, so a short
SHA, a full SHA, and a branch name for one commit each got their own key, and
reload-cloud's per-call ephemeral branches never shared an entry. The only
cross-branch fallback was `<prefix>main-`, which a run saves only when someone
dispatches `ref=main`, so it was effectively never present. Run 35938367902
restored nothing and built cold in 753 seconds.

The DerivedData key is now `<prefix>commit-<resolved sha>`, and the last
restore-key is the Xcode/runner/workspace prefix alone: the newest entry from
any commit or branch with the same toolchain and checkout path. That is safe to
adopt because tracked-source mtimes are already derived from blob ids, so every
file that differs from the cached build recompiles, and a failed cached build
already retries cold. The SPM cache gains the same prefix fallback, so a
Package.resolved bump no longer discards every other package checkout.

actions/cache scopes entries to the dispatch ref. A run dispatched on an
ephemeral branch still restores default-branch entries but saves where no
later run can read; the workflow now says so with a notice, and timings.json
records `cache_scope_ref` in place of the retired `cache_branch_key`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4dc19f59-753b-45e2-aca4-cd5e07b341b5

📥 Commits

Reviewing files that changed from the base of the PR and between 19144b6 and 980895d.

📒 Files selected for processing (4)
  • .github/workflows/ci-guards.yml
  • .github/workflows/reload-build.yml
  • tests/test-execution.toml
  • tests/test_ci_reload_build_cache_keys.py

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The reload-build workflow now builds macOS cache keys from the resolved commit and restores compatible caches across commits and branches. A new regression test checks the cache metadata and restore behavior, and CI runs that test.

Changes

Reload-build cache behavior

Layer / File(s) Summary
Cache metadata and restore behavior
.github/workflows/reload-build.yml
Cache metadata records the dispatch ref and resolved commit. SPM and DerivedData restore prefixes now include broader compatible-cache matches. Timing output uses cache_scope_ref.
Cache key regression tests
tests/test_ci_reload_build_cache_keys.py, tests/test-execution.toml, .github/workflows/ci-guards.yml
The test checks key consistency across ref spellings, restoration across commits, and fallback key contents. The test is registered in the linux-guard lane and runs in the app-host-cache matrix group.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 98089

The cache-key test covers the documented dispatch path, and no issue identified here prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 1 files. (3 skipped: 3… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS: The PR changes only reload-build cache metadata, cache restore prefixes, timing fields, and CI guard tests. The diff does not create or transport Cloud terminals, spawn cmux-tui clients or physi…
Cmux Swift Actor Isolation ✅ Passed PASS: The pull request changes only GitHub Actions YAML, a TOML registry, and a Python test. The authoritative diff contains no Swift or Objective-C production source changes, so it cannot introduce o…
Cmux Swift Blocking Runtime ✅ Passed The pull request changes only GitHub Actions workflow YAML and Python/TOML test registration files. The authoritative diff contains no Swift files and introduces no production Swift code. Therefore th…
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR changes only GitHub Actions cache workflow logic and reload-build cache tests. The authoritative diff contains no browser.* commands, WebKit/AppKit automation, socket-worker routing, proc…
Cmux Expensive Synchronous Load ✅ Passed PASS: The pull request changes only GitHub Actions workflow files and Python/TOML tests. The authoritative diff contains no Swift files or production Swift changes, so the expensive synchronous Swift …
Cmux Cache Substitution Correctness ✅ Passed PASS: The pull request changes only GitHub Actions YAML, TOML test registration, and a Python guard test. It does not change production Swift, TypeScript, or JavaScript code, and it does not introduce…
Cmux No Hacky Sleeps ✅ Passed PASS. The pull request changes GitHub Actions workflow YAML, test registration, and a Python regression test. The rule explicitly excludes GitHub Actions YAML, and the new test is deterministic test-o…
Cmux Algorithmic Complexity ✅ Passed No algorithmic-complexity failure is introduced. The production change in .github/workflows/reload-build.yml replaces ref-string key construction with constant-time metadata and adds cache restore p…
Cmux Swift Concurrency ✅ Passed PASS: The pull-request range changes only two YAML workflow files, one TOML registry entry, and one Python test. It changes no Swift file, and no added line matches the checked legacy Swift concurrenc…
Cmux Swift @Concurrent ✅ Passed PASS: The pull request changes only GitHub Actions workflow files, TOML, and a Python test. The authoritative diff contains no changed Swift source, and no changed @concurrent, nonisolated, `@Main…
Cmux Swift Package Boundaries ✅ Passed PASS: The authoritative PR diff changes only two YAML workflows, one TOML registry, and one Python test. It contains no .swift or Swift package source changes, so the Swift package-boundary rule is …
Cmux Swiftpm Lockfiles ✅ Passed PASS. The PR changes only workflow cache behavior and adds a cache-key test. It changes no Package.swift, Package.resolved, .gitignore, Xcode project, or package-reference files. The workflow st…
Cmux Swift Logging ✅ Passed PASS: The PR changes only YAML, TOML, and Python files. It adds no production Swift code or Swift logging. The added ::notice:: is GitHub Actions workflow output, and the print in the new Python t…
Cmux User-Facing Error Privacy ✅ Passed PASS. The diff changes only GitHub Actions cache logic, CI guard registration, and a test. The new ::notice:: and GITHUB_STEP_SUMMARY output are internal workflow diagnostics. timings.json is a …
Cmux Full Internationalization ✅ Passed PASS: The PR changes only GitHub Actions workflow logic, workflow comments/operational notices, test registration, and a test file. It adds no Swift UI text, app string-catalog or Info.plist entries, …
Cmux Swiftui State Layout ✅ Passed PASS: The pull request changes only YAML workflows, a TOML test registry, and a Python cache-key test. The authoritative diff contains no Swift or SwiftUI code and no state, layout, list-row, or rende…
Cmux Architecture Rethink ✅ Passed PASS: The PR changes only two YAML workflows, one TOML registry entry, and one Python test. The authoritative diff contains no Swift, Objective-C, SwiftUI, or AppKit source changes and no architectura…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The pull request changes only GitHub workflow files and Python/TOML test files. The authoritative diff contains no Swift changes and no standalone NSWindow, NSPanel, NSWindowController, SwiftUI …
Cmux Source Artifacts ✅ Passed PASS: The four changed paths are workflow configuration, test registration, and an intentional Python regression test. The new test creates its Git repository, fake xcodebuild, and output files in a…
Cmux No Test Or Debug Seam In Production Source ✅ Passed The pull request changes only workflow YAML, TOML test registry, and a Python test. It changes no Swift file under a production Sources/ path, so the no-test-or-debug-seam condition does not apply.
Title check ✅ Passed The title clearly and concisely describes the main change: commit-based reload-build cache keys with cross-branch fallback.
Description check ✅ Passed The description clearly explains the problem, cause, implementation, validation, and related scope. It provides detailed testing information. The template's checklist and review-trigger sections are n…
Full details: Docstring Coverage

Explanation

Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 1 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

Comment @coderabbitai help to get the list of available commands.

The spelling guard injected SOURCE_REF, which the fixed step no longer reads,
so it could not catch ref-text keying reintroduced under another name. It now
evaluates the step's own env block with each spelling substituted for every
ref-bearing expression, fails on an expression it does not model, and checks
the notice for dispatches off the default branch. It still fails against the
workflow on main. Also rewraps the workflow header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Pushed 5ff0959cd5. This addresses an independent review of 980895d58f.

  • Test gap: the ref-spelling test fed SOURCE_REF, which the fixed step no longer reads. That meant it could not catch ref-text keying if it came back under another env name. The test now evaluates the metadata step's own env: block, substituting each spelling for inputs.ref, github.ref_name, github.ref and github.head_ref. It fails on any expression it does not model, and it also asserts the off-default-branch notice. I checked that it still fails against main's workflow, where one commit gets five distinct keys, and passes on this branch. actionlint is clean.
  • Header comment: re-wrapped.

The review also raised two points I have not changed:

  • The DerivedData restore order is now exact commit, then the newest entry of any branch. That drops the same-branch preference. For reload-cloud's per-call branches that tier never matched. A long-lived feature branch passed as ref may now adopt a less similar tree. That can cost extra compile time but not correctness.
  • Blacksmith's transparent cache backend doesn't appear in the GitHub caches API. The notice assumes Blacksmith copies GitHub's dispatch-ref scoping, which I have not verified.

A live A/B check is in progress. Run A, 35944989183, is a cold baseline at 980895d58f. Run B will dispatch 5ff0959cd5 by short SHA once A has saved, and should restore A's entry through the generic fallback.

— Rivetmoss g1 🦉

@teamleaderleo
teamleaderleo merged commit ba85a1b into main Sep 24, 2026
48 checks passed
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 24, 2026
5b646b7 ci: apply the queue janitor threshold per runner pool (manaflow-ai#14131)
d49a1b1 ci: reuse the headless cmux-tui build in SDK conformance (manaflow-ai#14108)
ba85a1b ci: key reload-build caches on the commit and fall back across branches (manaflow-ai#14099)
27fb3bf ci: hand focused test-macos-suite dispatches to run-e2e.sh (manaflow-ai#14075)
d18c1b9 ci: let a failed compile admission mark a run doomed for the queue janitor (manaflow-ai#14129)
dfdce2c ci: bind pull request product reuse to the merge it compiled (manaflow-ai#14080)
afacff3 ci: sparse-checkout the Claude wrapper regression job (manaflow-ai#14088)
35a6bb1 ci: stop pinning remote-daemon macOS tests to the macOS 26 pool (manaflow-ai#14128)
1ba6d77 ci: run macOS jobs on GitHub-hosted runners alongside Blacksmith (manaflow-ai#14097)
587de87 Import CmuxWorkspaces where CodexTurnRestoreIntentPolicy names its liveness type (manaflow-ai#14123)

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci-macos.yml
#	.github/workflows/ci-queue-janitor.yml
#	.github/workflows/ci.yml
#	.github/workflows/cmux-tui-sdks.yml
#	.github/workflows/reload-build.yml
#	.github/workflows/remote-daemon.yml
#	.github/workflows/test-macos-suite.yml
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