ci(cmux-next): reuse the compiled DEV Release app when its inputs did not change

Merged
TE
12d ago
+653-0

Leo: "the incrementally, you know, tracking the latest commit or whatever, building it off the right way, building the right things, like reusing as much as you can"

What

Part 2 of 2. It depends on #24963, the key: job_input_fingerprint.py dev-release-product.

Today every DEV run compiles the Mac Release app, even when the merge changed nothing that goes into it. This PR wraps the DEV Release compile (cmux-scheme-compile in cmux-next.yml, push and dev_daily runs on feat-cmux-next) in a lookup and a record step:

  • Lookup (after Select Xcode, deadline 3 min, step timeout 5 min, continue-on-error):
    • computes the key on the checked-out tree;
    • finds the newest unexpired cmux-next-dev-release-product-<key> record from this repository's own push or dispatch run of cmux-next.yml on feat-cmux-next. Records from forks, pull requests, other branches and other workflows are refused;
    • downloads the cmux-next-dev-daily-unsigned-<sha> artifact that the record names and checks its size and SHA-256 against the record.
  • On a hit: the compile step uses those bytes and skips xcodebuild. A dev_daily run also skips the steps that only feed xcodebuild (cmux-tui fetch, Rust, acpmux, bun, node, web bundles). Push runs still run them because they also compile Debug.
  • On a miss: a missing record, an untrusted or expired one, a digest mismatch, an API or network error, or the deadline each give a miss. The job compiles exactly as before and never fails because of the lookup. The job summary prints hit (with the source run, commit and key) or miss (reason).
  • Record: after a successful compile or reuse and upload, the run writes manifest.json: key, recipe, commit, product artifact, SHA-256, and provenance (origin: compiled or reused, compiled_run_id, reused_from). It uploads the manifest as cmux-next-dev-release-product-<key>, kept 2 days like the app artifact it names. A reuse writes a record too, which keeps the chain alive.
  • Unchanged:
    • Signing, packaging and publishing still run fresh at the exact SHA. cmux-next-dev-daily.yml is untouched and stamps CMUXCommit itself.
    • The store is the Actions artifacts the DEV path already uses, so no new secrets or permissions (the job already has actions: read).
    • Switches: no_reuse dispatches skip the lookup, and vars.CI_NEXT_DEV_PRODUCT_REUSE=0 turns it off.
  • Scope guard (hq#3967): the script refuses unless GITHUB_WORKFLOW_REF is cmux-next.yml@refs/heads/feat-cmux-next. A test fails if any other workflow references the script or the record name. nightly.yml and the stable workflows are untouched.

Before (measure_next_build.py --dev-chain, last 12 DEV publishes, 2026-10-24)

compile_job (min)
median10.7
range9.6 to 14.6

On a warm mini (source run 40130016492): about 60 s of steps before the compile, then 533 s of xcodebuild, then 18 s of upload. A hit skips the xcodebuild and, on dev_daily, the ~45 s of feeder steps. Each hit saves the 10-14 min DEV compile job, not the older 20-35 min figure. Replaying the key over the last 199 merges, 25% would hit.

After: TBD. I will fill this in from the same harness once a product-unchanged merge has run after landing.

Success / failure

  • Success: a product-unchanged merge's DEV compile job takes ≤ ~60 s and its summary prints DEV Release product: hit.
  • Failure:
    • a stale product ships (an input outside the key, or a record whose bytes don't match);
    • a cache outage blocks or fails a build;
    • a reuse leaks into nightly or stable.

Risk

Medium-low, CI only.

  • (1) Staleness depends on the key's closure (part 1). It defaults to including everything, and its exclusions are pinned by tests.
  • (2) Trust: the GitHub-reported run must be a push or dispatch of cmux-next.yml on feat-cmux-next in this repository. Records from fork and pull request runs are never taken. The manifest must name its own run and that run's product artifact. The bytes must match the SHA-256 in the record.
  • (3) Availability: every error is a miss, the lookup has a 3 min deadline inside a 5 min step timeout, and every reuse step is continue-on-error.
  • (4) Provenance: on a hit, the published DEV app's bytes come from an earlier run with the same key. CMUXCommit is still the exact SHA, and the run's record names compiled_run_id.

Tests: tests/test_dev_release_product_reuse.py, run in the cmux-next checks job. It covers a hit, newest record wins, fallback past an expired product, digest mismatch, fork, pull request, wrong branch and wrong workflow, lane guard, an unreachable store, the CLI never failing, a record round trip, and the workflow scope. actionlint shows no new findings.

Agent session: cosmic-gray

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.