From 3e8e5d05a5585b1b244b7bc1649df7e0ff4a0256 Mon Sep 17 00:00:00 2001 From: Aziz Albahar Date: Sat, 20 Jun 2026 15:45:05 -0700 Subject: [PATCH 1/3] ci: aggregate required gates so renames/splits stop desyncing branch protection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch protection requires status checks by literal name, but recent CI churn (unit-test sharding renamed the `tests` job to `app-host unit tests (N/4)` in #6464; jobs split across ci.yml and test-ios.yml; new suites added) kept moving those names out from under the static required-checks list — stranding old names ("expected" forever, blocking every PR) and leaving new suites ungated. Fix: gate via aggregate summary jobs that reference suites by their job KEY (immune to display-name/shard changes), one per workflow. - ci.yml `tests-required-status` (reported as `tests`, already required): now also needs `swift-package-tests` and `agent-session-web-resources`, so a failure in either blocks merge. Skipped (path-filtered) is still allowed. - test-ios.yml: new `ios-tests` aggregate over `detect-ios-changes`, `package-conventions-lint`, `mobile-core-package`, `ios-simulator`, same skip-tolerant logic. Settings follow-up (after merge): add `ios-tests` to the main ruleset's required status checks. The `tests` change needs no settings change (same name). Optional cleanup: the individual web/release contexts can stay or be folded into the aggregates later. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 19 +++++++++++++ .github/workflows/test-ios.yml | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fad5f9f7aa5..2890e63efa37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -665,9 +665,16 @@ jobs: tests-required-status: name: tests + # Aggregate gate for the test/build suites in this workflow. Required by + # branch protection under the stable name "tests", and references each suite + # by its job KEY via needs:, so renaming/sharding a job's display name never + # desyncs the required-checks list (the failure mode from the #6464 sharding + # that stranded the old "tests" check). Add new ci.yml test/build jobs here. needs: - changes - tests + - swift-package-tests + - agent-session-web-resources if: ${{ always() }} runs-on: ${{ vars.LINUX_RUNNER || 'warp-ubuntu-latest-x64-4x' }} timeout-minutes: 5 @@ -698,8 +705,20 @@ jobs: print(f"app-host unit tests had unexpected result for macos={macos}: {tests['result']}", file=sys.stderr) sys.exit(1) + # The remaining test/build suites in this workflow gate too. A job that + # opts out via its own path filter reports "skipped", which is fine; a + # job that actually ran and failed must block the merge. + allowed = {"success", "skipped"} + for name in ("swift-package-tests", "agent-session-web-resources"): + result = needs[name]["result"] + if result not in allowed: + print(f"{name} did not pass: {result}", file=sys.stderr) + sys.exit(1) + print(f"changes.macos={macos}") print(f"app-host unit tests={tests['result']}") + for name in ("swift-package-tests", "agent-session-web-resources"): + print(f"{name}={needs[name]['result']}") PY swift-package-tests: diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index b4f15139863b..8dc2f74b723b 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -364,3 +364,52 @@ jobs: fi exit "$status" done + + ios-tests: + name: ios-tests + # Aggregate gate for this workflow's iOS suites. Add this check to the branch + # protection required list (it is the iOS sibling of ci.yml's "tests" gate). + # References each suite by job KEY via needs:, so renaming or sharding a job + # never desyncs the required-checks list. Add new iOS jobs to needs: here. + needs: + - detect-ios-changes + - package-conventions-lint + - mobile-core-package + - ios-simulator + if: ${{ always() }} + runs-on: ${{ vars.LINUX_RUNNER || 'warp-ubuntu-latest-x64-4x' }} + timeout-minutes: 5 + steps: + - name: Check iOS test routing + env: + IOS_NEEDS: ${{ toJSON(needs) }} + run: | + python3 - <<'PY' + import json + import os + import sys + + needs = json.loads(os.environ["IOS_NEEDS"]) + + # The routing job must succeed for its should_run/should_lint outputs to + # be trustworthy; the suites below run or skip based on those outputs. + if needs["detect-ios-changes"]["result"] != "success": + print(f"detect-ios-changes: {needs['detect-ios-changes']['result']}", file=sys.stderr) + sys.exit(1) + + # A suite that opted out via the routing filter reports "skipped", which + # is fine; a suite that actually ran and failed must block the merge. + allowed = {"success", "skipped"} + bad = { + name: data["result"] + for name, data in sorted(needs.items()) + if name != "detect-ios-changes" and data["result"] not in allowed + } + if bad: + for name, result in bad.items(): + print(f"{name} did not pass: {result}", file=sys.stderr) + sys.exit(1) + + for name, data in sorted(needs.items()): + print(f"{name}={data['result']}") + PY From 7f3216690c6c00dc34bc91facbf4b79f8e01db69 Mon Sep 17 00:00:00 2001 From: Aziz Albahar Date: Sat, 20 Jun 2026 15:54:08 -0700 Subject: [PATCH 2/3] ci: rename the sharded job key tests -> app-host-unit-tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the confusing name/key crossover left by #6464+#6474: a job KEYED `tests` that reported as "app-host unit tests", plus a gate keyed `tests-required-status` that reported as `tests`. Now the names line up: - `app-host-unit-tests` (key) -> reports "app-host unit tests (N/4)" — the suite - `tests` (key) -> reports "tests" — the required aggregate gate No settings change: the gate still reports under the required name `tests`. The two `needs:` references to the old matrix key (the gate and ci-status) and the gate's needs["..."] lookup are updated accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2890e63efa37..ed2044521d3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -336,7 +336,7 @@ jobs: DIRECT_DATABASE_URL: postgres://cmux:cmux@localhost:5432/cmux_test run: bun run test:db:behavior - tests: + app-host-unit-tests: needs: changes if: ${{ needs.changes.outputs.macos == 'true' }} name: app-host unit tests (${{ matrix.shard }}/4) @@ -663,7 +663,7 @@ jobs: CMUX_CLI_BIN="$CLI_BIN" python3 tests/test_pi_extension_install.py CMUX_CLI_BIN="$CLI_BIN" python3 tests/test_omp_extension_install.py - tests-required-status: + tests: name: tests # Aggregate gate for the test/build suites in this workflow. Required by # branch protection under the stable name "tests", and references each suite @@ -672,7 +672,7 @@ jobs: # that stranded the old "tests" check). Add new ci.yml test/build jobs here. needs: - changes - - tests + - app-host-unit-tests - swift-package-tests - agent-session-web-resources if: ${{ always() }} @@ -690,7 +690,7 @@ jobs: needs = json.loads(os.environ["TESTS_NEEDS"]) changes = needs["changes"] - tests = needs["tests"] + tests = needs["app-host-unit-tests"] macos = changes.get("outputs", {}).get("macos") if changes["result"] != "success": @@ -1913,8 +1913,8 @@ jobs: - web-typecheck - react-apps-check - web-db-migrations + - app-host-unit-tests - tests - - tests-required-status - swift-package-tests - agent-session-web-resources - tests-build-and-lag From 2286b9e3f70d41a6da156b0192e6d9c2228b7e2d Mon Sep 17 00:00:00 2001 From: Aziz Albahar Date: Sat, 20 Jun 2026 15:59:48 -0700 Subject: [PATCH 3/3] ci: track the tests->app-host-unit-tests rename in workflow guards The job-key rename moved the macOS app-host matrix to `app-host-unit-tests` and gave the key `tests` to the required aggregate gate. Update the guards that asserted on the old keys: - test_ci_self_hosted_guard.sh: assert the paid-macOS-runner requirement against `app-host-unit-tests` (the matrix), not `tests` (now a linux gate). - test_ci_change_areas.py: ci-status routed-jobs list and the gate-block test now reference `app-host-unit-tests` (matrix) and `tests` (gate). All workflow-guard-tests steps pass locally (self-hosted guard, change-areas, sharding validator). Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_ci_change_areas.py | 6 +++--- tests/test_ci_self_hosted_guard.sh | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_ci_change_areas.py b/tests/test_ci_change_areas.py index c33a42b13dea..682a71e204cc 100644 --- a/tests/test_ci_change_areas.py +++ b/tests/test_ci_change_areas.py @@ -393,8 +393,8 @@ def test_ci_status_job_accepts_skipped_routed_jobs() -> None: "web-typecheck", "react-apps-check", "web-db-migrations", + "app-host-unit-tests", "tests", - "tests-required-status", "tests-build-and-lag", "release-ghostty-cli-helper", "release-build", @@ -407,11 +407,11 @@ def test_ci_status_job_accepts_skipped_routed_jobs() -> None: def test_required_tests_status_waits_for_app_host_matrix() -> None: - block = workflow_job_block("tests-required-status") + block = workflow_job_block("tests") assert "name: tests" in block assert " - changes" in block - assert " - tests" in block + assert " - app-host-unit-tests" in block assert "if: ${{ always() }}" in block assert 'macos == "true" and tests["result"] != "success"' in block assert 'tests["result"] not in {"success", "skipped"}' in block diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index 3e4788707cb1..4366cf0f1058 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -711,7 +711,8 @@ check_no_bare_github_hosted_runners() { # switch is a single repo-variable flip with no PR. A bare GitHub-hosted # label (ubuntu-*, macos-NN) cannot be redirected, so it is forbidden. # Bare paid-provider labels (blacksmith-*, warp-*, depot-*) stay allowed for - # deliberate single-runner pins such as the testmanagerd-wedged `tests` job. + # deliberate single-runner pins such as the testmanagerd-wedged + # `app-host-unit-tests` job. local hits hits="$(grep -rnE "runs-on:[[:space:]]*(ubuntu-[a-z0-9.]+|macos-[a-z0-9]+)[[:space:]]*$" "$ROOT_DIR/.github/workflows" || true)" if [[ -n "$hits" ]]; then @@ -793,7 +794,7 @@ check_no_self_hosted_fleet_runners() { # ci.yml jobs check_no_bare_github_hosted_runners check_no_self_hosted_fleet_runners -check_macos_runner "$CI_FILE" "tests" +check_macos_runner "$CI_FILE" "app-host-unit-tests" check_macos_runner "$CI_FILE" "tests-build-and-lag" check_macos_runner "$CI_FILE" "release-ghostty-cli-helper" check_macos_runner "$CI_FILE" "release-build"