ci(nightly): start the shipped Release build from empty DerivedData

Merged
LA
19d ago
+42-22

Summary

The classic NIGHTLY build on the owned mini (cmux15-glaeda) linked build-universal to a persistent per-channel DerivedData tree and built on it with no clean. An incremental Xcode build can keep stale autolink and force-load data in objects that it did not recompile after an import set changed (the import-set defect found in the 2026-10-18 build audit). Those objects then link into the app that build-sign-notarize-nightly signs and ships.

This change:

  • Replaces Prepare persistent Release DerivedData with Start Release DerivedData empty. Every runner, owned mini or Blacksmith, now deletes build-universal and starts from an empty tree.
  • Keeps the warm state that is safe: the content-keyed Xcode compilation cache that is restored into build-universal/CompilationCache.noindex (its keys include the explicit module dependencies) and the Swift package checkouts in .spm-cache.
  • On an owned mini, deletes this channel's old ~/Library/Developer/Xcode/DerivedData/cmux-nightly-<channel>-<64-hex> trees. The match is exact, so the step does not touch another channel's trees (for example cmux-nightly-rc-*). The nightly-next track keeps its state under ~/Library/Caches/cmux-nightly-next, which this step never touches; manaflow-ai/cmux#21762 removes that state on feat-cmux-next.
  • Changes tests/test_nightly_universal_build.sh to require the empty start before the compilation cache restore and to refuse a persistent link.

Blacksmith already started from an empty tree, so its behavior does not change.

Added build time

Measured: none today. Since at least 2026-10-13, cmux15-glaeda has not built attempt 1. It refuses at job start (2026-10-17 and 2026-10-18: toolchain drifted from the m4pro-48 class receipt: diffRustcVersion) or does not take the job in its budget (2026-10-13 to 2026-10-16). The rescue then runs attempt 2 on Blacksmith. Thus every NIGHTLY that shipped in this period was already a clean-tree build.

Estimate for when the mini takes jobs again: the Blacksmith clean-tree builds (empty DerivedData plus the restored compilation cache, 15 runs from 2026-10-13 to 2026-10-18) took 188-914 s in Build nightly app (Release) (median 631 s). The Release cmux target is a whole-module compile that runs again on each push with or without warm DerivedData, so the warm tree saved mostly dependency targets and Rust helper output. The compilation cache already covers most of the dependency compiles. Expected added time on the mini: about 1-5 minutes per build. The first build after the mini returns can be fully cold: the DerivedData path changes, so its own cache lineage can miss until the save step writes it again.

Testing

  • ruby -e 'require "psych"; Psych.load_file(".github/workflows/nightly.yml")' parses the workflow.
  • tests/test_nightly_universal_build.sh runs in CI Guards (not on a local Mac, per team policy). The test commit comes first and fails on the old workflow.
  • Not run: a real owned-mini NIGHTLY. The mini refuses jobs today (see above).

Changelog

none

Merge note

The next main-to-feat-cmux-next sync touches this step there too. manaflow-ai/cmux#21762 already uses the same text on feat-cmux-next, so that sync is clean when #21762 lands first.

Summary by CodeRabbit

  • Chores
    • Nightly app builds now start with a clean build workspace instead of reusing prior build data.
    • On applicable build runners, stale build data for the active channel is cleared; data for other channels and the separate next-channel cache is left untouched.
    • Regression checks now verify that nightly builds use a clean workspace.