Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -663,11 +663,18 @@ 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
# 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
- app-host-unit-tests
- swift-package-tests
- agent-session-web-resources
if: ${{ always() }}
runs-on: ${{ vars.LINUX_RUNNER || 'warp-ubuntu-latest-x64-4x' }}
timeout-minutes: 5
Expand All @@ -683,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":
Expand All @@ -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:
Expand Down Expand Up @@ -1894,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
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +368 to +415

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 ios-tests required-check will block PRs that never trigger test-ios.yml

test-ios.yml has a paths: trigger filter. For a PR that touches only web/**, a server-side Go file, or any other path outside the listed patterns, the entire workflow never runs — meaning ios-tests produces no check status at all. GitHub reports the check as "Expected" (not "skipped"), and a required check that is absent blocks the merge.

The PR description says "skip = pass here," but that only describes jobs that opt out inside a running workflow (e.g., ios-simulator skipping because should_run=false). It does not cover the case where the workflow itself never triggers. If ios-tests is added to the ruleset's required list under the default "must be present and passing" mode, every web-only, Go-only, or other non-iOS-path PR will be permanently blocked.

The safe configuration options are: (a) switch the GitHub Ruleset entry to "required if triggered" / allow-if-skipped mode, or (b) remove the paths: filter from the on: pull_request: trigger so the workflow always runs and internal job conditions handle the skip logic (the same model ci.yml uses).

6 changes: 3 additions & 3 deletions tests/test_ci_change_areas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions tests/test_ci_self_hosted_guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Loading