Skip to content

Speed up macOS CI with unit test sharding - #6464

Merged
lawrencecchen merged 1 commit into
mainfrom
feature-cut-cicd-time
Jun 20, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
feature-cut-cicd-time

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • shard app-host cmuxTests across four deterministic CI shards
  • split very large XCTestCase suites into method selectors so one huge class cannot dominate a shard
  • move Swift package tests and agent-session web-resource tests out of the app-host unit job
  • add a post-XCTest-summary xcodebuild grace timeout for the known app-host hang path

Validation

  • actionlint .github/workflows/ci.yml
  • ruby -e 'require "yaml"; YAML.load_file(".github/workflows/ci.yml"); puts "yaml ok"'\n- python3 scripts/ci/cmux_unit_test_shard.py --validate\n- python3 -m py_compile scripts/ci/cmux_unit_test_shard.py scripts/ci/xcodebuild_noninteractive.py\n- git diff --check\n- synthetic xcodebuild_noninteractive.py post-summary timeout smoke test\n\n## Timing\n- baseline CI: 28m55s overall, with the old tests job at 26m28s\n- first CI pass after suite sharding: green, but slowest shard still 19m05s\n- second pass splits oversized XCTest suites by method and trims post-summary xcodebuild hangs; waiting on CI for final timing

Note

Medium Risk
Test coverage now depends on sharding plus shard-1 focused gates being complete; mis-partitioning could skip suites. Post-test xcodebuild termination is intentional but changes pass/fail timing semantics for hung app-host runs.

Overview
macOS app-host unit tests now run as a 4-shard matrix (fail-fast: false). Each shard gets its own DerivedData path and runs only a subset of cmuxTests via -only-testing args from new scripts/ci/cmux_unit_test_shard.py (weight-balanced buckets, method-level split for suites with ≥40 tests, duplicate detection). BrowserSystemProxyMirrorTests and GhosttyOptionAsAltModsTests stay out of shards and run only on shard 1 as dedicated non-tolerant gates. The main unit step uses run-app-host-xcodebuild.sh (per-machine GUI test lock) instead of calling xcodebuild_noninteractive.py directly.

Job splits: Swift package swift test moves to swift-package-tests; agent-session web build/test + generated resource diff moves to agent-session-web-resources on Linux. CLI no-socket regressions stay on shard 1 after the unit build. ci-status depends on the new jobs.

Hang mitigation: xcodebuild_noninteractive.py gains CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS (45s in CI)—after the “Selected tests” summary it can terminate a stuck xcodebuild and exit 0 on pass / 125 on fail. run-in-console-session.sh forwards that env var. Unit test logs use per-shard paths under RUNNER_TEMP instead of /tmp/test-output.txt.

Guards: workflow step validates sharding; tests/test_ci_cmux_unit_test_shard.py, updated SPM retry and noninteractive helper tests. Minor workflow cleanups (batched GITHUB_OUTPUT, find for SPM package paths).

Reviewed by Cursor Bugbot for commit 1c14489. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Chores / CI
    • Improved macOS “app-host unit tests” sharding using deterministic, weight-based XCTest selector selection with shard-specific build output paths.
    • Added selector discovery/validation tooling (including duplicate detection and shard-content verification) and streamlined regression coverage to align with the sharded runs.
    • Enhanced non-interactive test execution with a configurable post-test timeout and forwarded the related timeout setting into console sessions.
    • Added new CI jobs for Swift package tests and web resource generation, and updated CI status aggregation.
    • Reformatted virtual-display environment exports for more reliable job coordination.

@vercel

vercel Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 20, 2026 2:04am
cmux-staging Building Building Preview, Comment Jun 20, 2026 2:04am

@coderabbitai

coderabbitai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds scripts/ci/cmux_unit_test_shard.py, a Python tool that discovers Swift cmuxTests suites by regex scan, computes weight from test-token count, and distributes them across 4 shards using greedy bin-packing with SHA256-based tie-breaking. The macOS unit-test CI job is converted to a 4-shard matrix strategy with shard-1-exclusive regression coverage. Two new macOS jobs (swift-package-tests and agent-session-web-resources) are introduced. Post-test timeout infrastructure is added to xcodebuild_noninteractive.py and run-in-console-session.sh. Environment-variable output writes are consolidated into grouped multi-line blocks.

Changes

macOS CI Test Sharding and New Job Infrastructure

Layer / File(s) Summary
cmux_unit_test_shard.py: suite discovery and deterministic sharding
scripts/ci/cmux_unit_test_shard.py
New script with TestSelector and SuiteDeclaration frozen dataclasses, discover_selectors() (regex scan of cmuxTests Swift sources, weight by @Test/func test... count, optionally split large suites ≥LARGE_SUITE_METHOD_THRESHOLD into per-method selectors, duplicate detection with exit failure), shard_selectors() (greedy bin-packing into N buckets using descending-weight order with SHA256-based tie-breaker for determinism), write_output() (writes -only-testing:<selector_id> lines), and main() CLI supporting --validate (prints selector counts), --list (prints identifier/weight/location), and shard modes (--shard-index/--shard-total/--output) with error handling for missing/invalid arguments.
Unit-test job sharding and test-selection wiring
.github/workflows/ci.yml
Adds workflow-guard-tests step calling cmux_unit_test_shard.py --validate. Converts macOS app-host unit-test job to strategy.matrix.shard: [1,2,3,4] matrix. Makes DERIVED_DATA_PATH shard-specific. Gates split-theme appearance regression to matrix.shard == 1. Invokes cmux_unit_test_shard.py per shard to compute shard-specific ONLY_TESTING_ARGS, writes to per-shard file, and injects selectors into xcodebuild test invocation. Updates test output capture to use shard-specific $RUNNER_TEMP files, including SwiftPM-retry path.
Shard 1 exclusive regression coverage
.github/workflows/ci.yml
Adds Ghostty theme-picker-helper regression step (gated to shard 1) and CLI "no-socket" regressions that locate cmux CLI binary in shard-specific DerivedData, build the CLI target, and run CLI-focused Python test scripts against the binary. Removes unconditional CLI regressions from unit-test job flow.
Post-test timeout infrastructure
scripts/ci/xcodebuild_noninteractive.py, scripts/ci/run-in-console-session.sh
Adds post_test_timeout_seconds() function to parse CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS (returns None for missing/invalid, exits with code 2 on parse error). Introduces SELECTED_TESTS_DONE_RE regex and SUCCESS_MARKER constant to detect terminal "Selected tests" completion and success markers. Extends main() to initialize post-test deadline and tracking flags. During PTY read loop, adjusts select() timeout to respect post-test deadline, sets post_test_timed_out when deadline elapses, detects terminal summary markers, and on timeout returns 0 only if terminal XCTest summary and passing marker were observed; otherwise returns TIMEOUT_EXIT_CODE. Removes prior explicit idle-timeout termination logic. Forwards the timeout env var through run-in-console-session.sh into the Aqua session.
swift-package-tests macOS CI job
.github/workflows/ci.yml
Introduces job with Xcode selection, GhosttyKit caching/download, Rust setup, and find-based package directory discovery under Packages/ (replacing ls globbing) before running SwiftPM unit tests.
agent-session-web-resources macOS CI job
.github/workflows/ci.yml
Introduces job that performs Bun-based install/build/test for Agent Session web resources and validates generated outputs via git diff --exit-code against expected resource directories.
CI environment-variable output formatting and job aggregation
.github/workflows/ci.yml
Refactors emit_all_areas output block to grouped multi-line echo. Reformats four separate virtual-display environment-variable sections (transient and persistent CMUX_VDISPLAY_LOCK_DIR/TOKEN and VDISPLAY_* variables) to grouped { ... } >> "$GITHUB_ENV" blocks. Updates ci-status.needs to include both new macOS jobs.
Test coverage for sharding and post-test timeout
tests/test_ci_cmux_unit_test_shard.py, tests/test_ci_unit_test_spm_retry.sh, tests/test_ci_xcodebuild_noninteractive_helper.py
New test script validates shard helper behavior by creating fixture with large suite and extension methods, running shards 1–4, and asserting extension methods appear exactly once and large suites are split into per-method selectors. Updates spm-retry test to expect shard-specific output files. Adds post-test timeout test coverage verifying exit codes (0 for pass, 124 for timeout) with different "Selected tests" summaries.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#6295: Both PRs modify scripts/ci/xcodebuild_noninteractive.py timeout handling by adding new timeout windows and shared deadline/termination logic with TIMEOUT_EXIT_CODE.
  • manaflow-ai/cmux#6401: Main PR builds on this by updating scripts/ci/run-in-console-session.sh to forward the CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS env var into the Aqua session environment.

Poem

🐇 Hop, hop, hooray — my tests now shard in four ways!
A Python script weighs suites by test tokens with care,
Then greedy-bins them cross shards — load balanced fair.
New jobs for packages and resources, swift and bright,
While post-test timeouts keep the PTY-watch tight.
Parallel testing hopping makes CI blur with speed! 🚀


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error The new CLI/CLISocketPathResolver.swift file contains nested full-collection scans violating the complexity rule: discoverTaggedSockets() iterates directory entries (~100+), and per-entry calls isK... Precompute normalized socket path forms into a Set and do one-pass directory iteration with O(1) Set lookups instead of per-entry nested .contains scans across known paths.
Cmux Swiftpm Lockfiles ❌ Error 45 cmux-owned Package.swift files changed without corresponding Package.resolved diffs: Packages/Shared/{CMUXAuthCore,CMUXMobileCore,CmuxAgentChat,CmuxSyncStore}, Packages/iOS/{CmuxAgentChatUI,Cmux... Include package-local Package.resolved diffs for all 45 packages with SwiftPM dependency declarations that were added/modified.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: sharding unit tests across multiple CI jobs to reduce overall test duration.
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 Swift Actor Isolation ✅ Passed PR contains no production Swift code changes—only CI/workflow YAML, Python, and Shell test infrastructure scripts. Swift actor isolation check not applicable.
Cmux Swift Blocking Runtime ✅ Passed PR contains no production Swift code changes—only CI workflow YAML, Python/shell scripts, and test infrastructure. Check scope requires "production Swift changes" to trigger.
Cmux Expensive Synchronous Load ✅ Passed PR contains only CI/infrastructure changes (workflows, Python, shell scripts) with no production Swift modifications, so the expensive sync load rule does not apply.
Cmux Cache Substitution Correctness ✅ Passed Custom check applies only to production Swift/TS/JS changes. This PR modifies only CI infrastructure (YAML, Python, Shell scripts, tests)—no production code.
Cmux No Hacky Sleeps ✅ Passed All sleeps in the PR are either existing code (not introduced or worsened), test-only scaffolding, or GitHub Actions workflow YAML (out of scope per the rule). The pre-existing sleep in xcodebuild_...
Cmux Swift Concurrency ✅ Passed PR contains no cmux-owned Swift code changes—only CI/test infrastructure (YAML, Python, Shell scripts). Check is not applicable to this PR.
Cmux Swift @Concurrent ✅ Passed Pull request contains no Swift source file changes—only CI workflow (YAML), Python, and shell script modifications. Custom check for Swift @concurrent annotations is not applicable.
Cmux Swift File And Package Boundaries ✅ Passed PR contains no production Swift file changes; check applies only to Swift changes. All modifications are CI infrastructure (YAML, Python, shell scripts).
Cmux Swift Logging ✅ Passed No production Swift code is modified in this PR; all changes are to CI workflows, Python/Shell scripts, and test infrastructure, which are outside the scope of the Swift logging rule.
Cmux User-Facing Error Privacy ✅ Passed All PR changes are CI infrastructure scripts and tests (allowed by rules: "Developer-only comments, tests, docs, and operational runbooks"). No product-facing code modified; error messages are deve...
Cmux Full Internationalization ✅ Passed PR contains only CI infrastructure changes (.github/workflows/ci.yml, scripts/ci/*.py, test files). No user-facing Swift text, web UI, localization catalogs modified, or app source code changed. Al...
Cmux Swiftui State Layout ✅ Passed PR contains no .swift or SwiftUI code changes—only CI workflow YAML, Python scripts, and shell scripts. Check is not applicable.
Cmux Architecture Rethink ✅ Passed Swift changes remove architectural anti-patterns without introducing new ones: eliminates @State observers (TabItemView.observedIsActive/.onReceive), cache-sync duplication (liveSessionIDBySurfaceI...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No Swift source code changes in this PR; check only applies to Swift changes. All modifications are CI infrastructure (YAML, Python, shell scripts).
Cmux Source Artifacts ✅ Passed All PR-changed files are hand-written source, scripts, tests, and configuration (.github/workflows/ci.yml, scripts/ci/.py/.sh, tests/.py/.sh). None are local tool output, generated logs, screen...
Cmux No Test Or Debug Seam In Production Source ✅ Passed No Swift files under Sources/ were modified; PR contains only CI workflow YAML and Python/Shell infrastructure scripts.
Description check ✅ Passed PR description comprehensively covers all required sections: clear summary of changes, detailed validation steps performed, and timing metrics from CI runs.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-cut-cicd-time

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 and usage tips.

Comment thread .github/workflows/ci.yml
@lawrencecchen
lawrencecchen force-pushed the feature-cut-cicd-time branch from 995357c to 1d9f3a6 Compare June 20, 2026 00:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 609-612: The checkout step using
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd needs to include an
additional configuration parameter for security. Add persist-credentials: false
to the with section of this checkout step, placing it alongside the existing
submodules: recursive parameter to prevent the GITHUB_TOKEN from remaining in
the local git config after the step completes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b0a1cd4-1896-407c-8f08-8d911af433a6

📥 Commits

Reviewing files that changed from the base of the PR and between d886638 and 995357c.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • scripts/ci/cmux_unit_test_shard.py

Comment thread .github/workflows/ci.yml
Comment on lines +609 to +612
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding persist-credentials: false to new checkout steps.

The checkout step doesn't set persist-credentials: false, which means the GITHUB_TOKEN remains in the local git config. While consistent with existing checkout steps in this workflow, adding this for new code would improve security posture by preventing accidental credential exposure.

       - name: Checkout
         uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
         with:
           submodules: recursive
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 609-612: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 609 - 612, The checkout step using
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd needs to include an
additional configuration parameter for security. Add persist-credentials: false
to the with section of this checkout step, placing it alongside the existing
submodules: recursive parameter to prevent the GITHUB_TOKEN from remaining in
the local git config after the step completes.

Source: Linters/SAST tools

@greptile-apps

greptile-apps Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR speeds up macOS CI by running cmuxTests as a 4-shard matrix job with weight-balanced, deterministic test selection, and reduces wall-clock time further by adding a configurable post-test grace timeout that terminates a stuck xcodebuild once the "Selected tests" summary line has been emitted. It also extracts Swift package tests and agent-session web resource checks into separate, independently-runnable jobs.

  • 4-shard matrix (fail-fast: false) replaces the single tests job; a new cmux_unit_test_shard.py script discovers all cmuxTests suites, splits large ones (≥40 methods) by individual method selectors, and weight-balances selectors across shards; BrowserSystemProxyMirrorTests and GhosttyOptionAsAltModsTests are kept in dedicated focused-gate steps on shard 1 and are excluded from the shard pool entirely.
  • Post-test timeout (CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS=45) is added to xcodebuild_noninteractive.py; once the "Selected tests (passed|failed)" line is seen, a 45-second deadline is armed and, if xcodebuild hangs past it, the process is terminated and exits 0/125 depending on the result.
  • New jobs: swift-package-tests and agent-session-web-resources are both added as ci-status dependencies.

Confidence Score: 4/5

Safe to merge with one test-coverage gap to address: override func test… methods in large suites will be silently omitted from the method-level split and will not run under the sharded build.

The sharding logic, post-test timeout, and job splits are all well-implemented with tests. One gap in XCTEST_METHOD_RE means override func test… methods in any suite that crosses the 40-method threshold get no -only-testing selector and are silently skipped across all shards. This is a real but narrow coverage hole — it only affects suites large enough to be method-split that also override a base-class test method.

scripts/ci/cmux_unit_test_shard.py — the XCTEST_METHOD_RE modifier alternation needs override (and optionally open) added.

Important Files Changed

Filename Overview
.github/workflows/ci.yml Adds 4-shard matrix to the tests job, restricts dedicated focused gates to shard 1 only, splits swift-package-tests and agent-session-web-resources into separate jobs, adds CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS=45.
scripts/ci/cmux_unit_test_shard.py New sharding script with one gap: XCTEST_METHOD_RE misses override func test… methods in large-suite splits, causing silent omission from sharded runs.
scripts/ci/xcodebuild_noninteractive.py Adds post-test grace timeout: arms a 45s deadline after the Selected tests summary, terminates and exits 0/125. Interacts correctly with the existing idle timeout.
tests/test_ci_cmux_unit_test_shard.py Behavioral tests for sharding: extension method coverage, large-suite method-split, focused gate exclusion.
tests/test_ci_xcodebuild_noninteractive_helper.py Three smoke tests for post-test timeout: passing/noisy-passing/failing summary exit codes.

Reviews (3): Last reviewed commit: "Speed up macOS CI with unit test shardin..." | Re-trigger Greptile


SUITE_RE = re.compile(
r"^(?:@[A-Za-z_][A-Za-z0-9_]*(?:\([^)]*\))?\s+)*"
r"(?:(?:final|private|fileprivate|internal|public)\s+)*"

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.

P2 SUITE_RE does not include open or package as valid access-level modifiers. A declaration like open class BrowserSystemProxyMirrorTests or package class FooTests starts with a token not in the alternation, so the regex fails to match and that suite is silently excluded from every shard. Any such suite would never run via the sharded path. package is available since Swift 5.9; open has always been part of the access-control grammar.

Suggested change
r"(?:(?:final|private|fileprivate|internal|public)\s+)*"
r"(?:(?:final|open|package|private|fileprivate|internal|public)\s+)*"

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/ci/xcodebuild_noninteractive.py`:
- Around line 212-213: The check for EXPECTED_FAILURE_SUMMARY_RE should only be
performed after the terminal-summary phase has been detected, not throughout the
entire rolling window. Currently, the pattern matching for
saw_expected_failure_summary at the location checking
EXPECTED_FAILURE_SUMMARY_RE.search(prompt_window) and the similar check at lines
242-244 can match earlier output and incorrectly set the flag. Add a condition
to ensure the expected-failure pattern check only executes after the
terminal-summary has been detected by checking the state of
terminal_summary_found or equivalent flag before evaluating
EXPECTED_FAILURE_SUMMARY_RE, and apply this same condition to all instances
where expected-failure detection occurs.
- Around line 207-210: The post_test_deadline is being recalculated and reset
every iteration when the SELECTED_TESTS_DONE_RE marker is still present in
prompt_window, continuously extending the timeout. To fix this, only set
post_test_deadline once when the terminal test summary is first detected. Add a
condition to check whether post_test_deadline has already been set or whether
saw_terminal_test_summary was already True before the current iteration before
reassigning the deadline value in that block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5f61ad7e-3272-43db-88bf-4e20163bd6a2

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9f3a6 and ced2fe1.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • scripts/ci/cmux_unit_test_shard.py
  • scripts/ci/run-in-console-session.sh
  • scripts/ci/xcodebuild_noninteractive.py

Comment thread scripts/ci/xcodebuild_noninteractive.py Outdated
Comment thread scripts/ci/xcodebuild_noninteractive.py Outdated
@lawrencecchen
lawrencecchen force-pushed the feature-cut-cicd-time branch from ced2fe1 to 229623d Compare June 20, 2026 01:36
@blacksmith-sh

This comment has been minimized.

Comment thread scripts/ci/xcodebuild_noninteractive.py
@lawrencecchen
lawrencecchen force-pushed the feature-cut-cicd-time branch 2 times, most recently from 08589b7 to 1a45023 Compare June 20, 2026 01:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/ci/cmux_unit_test_shard.py`:
- Around line 19-24: The current implementation counts both `@Test` and XCTest
methods in the weight calculation using TEST_TOKEN_RE, but when splitting large
suites (LARGE_SUITE_METHOD_THRESHOLD of 40 or more), it only extracts and emits
XCTest selectors via XCTEST_METHOD_RE, silently dropping any `@Test` cases. To fix
this, either prevent suite splitting for mixed suites that contain both `@Test`
decorators and XCTest methods, or extend the selector extraction logic to also
capture and emit Swift Testing method selectors (using the `@Test` pattern) in
addition to XCTest selectors. Ensure that when a suite has both types of test
methods and meets the large-suite threshold, either the entire suite remains
unsplit or all test types are included in the sharded output.

In `@scripts/ci/xcodebuild_noninteractive.py`:
- Around line 56-61: In the except ValueError block that handles the
CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS parsing error, the
raise SystemExit(2) statement is directly re-raising without suppressing the
exception context chain, which violates Ruff B904. Add "from None" to the raise
SystemExit(2) statement to explicitly break the exception chaining and keep the
CLI error output concise without the original ValueError traceback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 664ebe8b-e221-44c7-9b24-1c7fe94044fb

📥 Commits

Reviewing files that changed from the base of the PR and between ced2fe1 and 229623d.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • scripts/ci/cmux_unit_test_shard.py
  • scripts/ci/run-in-console-session.sh
  • scripts/ci/xcodebuild_noninteractive.py

Comment on lines +19 to +24
TEST_TOKEN_RE = re.compile(r"(^|\s)(@Test\b|func\s+test[A-Za-z0-9_]*\s*\()")
XCTEST_METHOD_RE = re.compile(
r"^\s*(?:(?:final|private|fileprivate|internal|public)\s+)*"
r"func\s+(test[A-Za-z0-9_]*)\s*\("
)
LARGE_SUITE_METHOD_THRESHOLD = 40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve @Test cases when splitting oversized suites.

weight includes Swift Testing @Test tokens, but the large-suite branch only emits func test... XCTest selectors and then continues. A mixed suite with ≥40 XCTest methods would silently skip its @Test cases in the sharded CI run; keep mixed suites at suite level or add Swift Testing method-selector support.

🐛 Proposed guard for mixed Swift Testing/XCTest suites
 TEST_TOKEN_RE = re.compile(r"(^|\s)(`@Test`\b|func\s+test[A-Za-z0-9_]*\s*\()")
+SWIFT_TEST_TOKEN_RE = re.compile(r"(^|\s)`@Test`\b")
 XCTest_METHOD_RE = re.compile(
     r"^\s*(?:(?:final|private|fileprivate|internal|public)\s+)*"
     r"func\s+(test[A-Za-z0-9_]*)\s*\("
 )
             body = lines[line_number - 1 : next_line - 1]
             weight = max(1, sum(1 for line in body if TEST_TOKEN_RE.search(line)))
+            has_swift_testing_tests = any(
+                SWIFT_TEST_TOKEN_RE.search(line) for line in body
+            )
             suite_identifier = f"cmuxTests/{name}"
             method_matches = [
                 (line_number + offset, match.group(1))
                 for offset, body_line in enumerate(body)
                 if (match := XCTEST_METHOD_RE.match(body_line))
@@
-            if len(method_matches) >= LARGE_SUITE_METHOD_THRESHOLD:
+            if (
+                not has_swift_testing_tests
+                and len(method_matches) >= LARGE_SUITE_METHOD_THRESHOLD
+            ):

Also applies to: 59-72

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/cmux_unit_test_shard.py` around lines 19 - 24, The current
implementation counts both `@Test` and XCTest methods in the weight calculation
using TEST_TOKEN_RE, but when splitting large suites
(LARGE_SUITE_METHOD_THRESHOLD of 40 or more), it only extracts and emits XCTest
selectors via XCTEST_METHOD_RE, silently dropping any `@Test` cases. To fix this,
either prevent suite splitting for mixed suites that contain both `@Test`
decorators and XCTest methods, or extend the selector extraction logic to also
capture and emit Swift Testing method selectors (using the `@Test` pattern) in
addition to XCTest selectors. Ensure that when a suite has both types of test
methods and meets the large-suite threshold, either the entire suite remains
unsplit or all test types are included in the sharded output.

Comment on lines +56 to +61
except ValueError:
print(
"CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS must be numeric",
file=sys.stderr,
)
raise SystemExit(2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path

path = Path("scripts/ci/xcodebuild_noninteractive.py")
text = path.read_text()
start = text.index("def post_test_timeout_seconds")
end = text.index("\ndef terminate_child", start)
block = text[start:end]

if "except ValueError:" in block and (
    "raise SystemExit(2) from None" not in block
    and "raise SystemExit(2) from err" not in block
):
    raise SystemExit("post_test_timeout_seconds still violates Ruff B904")
PY

Repository: manaflow-ai/cmux

Length of output: 111


🏁 Script executed:

cat -n scripts/ci/xcodebuild_noninteractive.py | sed -n '50,70p'

Repository: manaflow-ai/cmux

Length of output: 788


🏁 Script executed:

python3 - <<'PY'
import subprocess
import sys

# Check if ruff recognizes B904
result = subprocess.run(
    ["python3", "-m", "ruff", "rule", "B904"],
    capture_output=True,
    text=True
)

if result.returncode == 0:
    print("Ruff B904 rule info:")
    print(result.stdout)
else:
    print("Note: ruff not installed or B904 not recognized")
    print("stderr:", result.stderr)
PY

Repository: manaflow-ai/cmux

Length of output: 1380


Suppress the parse exception context for Ruff B904.

Line 61 raises SystemExit directly inside except ValueError, which violates the B904 check. Use from None to explicitly break exception chaining and keep the CLI error concise.

🔧 Proposed fix
     except ValueError:
         print(
             "CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS must be numeric",
             file=sys.stderr,
         )
-        raise SystemExit(2)
+        raise SystemExit(2) from None
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 61-61: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/xcodebuild_noninteractive.py` around lines 56 - 61, In the except
ValueError block that handles the
CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS parsing error, the
raise SystemExit(2) statement is directly re-raising without suppressing the
exception context chain, which violates Ruff B904. Add "from None" to the raise
SystemExit(2) statement to explicitly break the exception chaining and keep the
CLI error output concise without the original ValueError traceback.

Source: Linters/SAST tools

@lawrencecchen
lawrencecchen force-pushed the feature-cut-cicd-time branch from 1a45023 to 7cba2f9 Compare June 20, 2026 01:53
@lawrencecchen
lawrencecchen force-pushed the feature-cut-cicd-time branch from 7cba2f9 to 1c14489 Compare June 20, 2026 01:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
scripts/ci/cmux_unit_test_shard.py (1)

14-25: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mixed Swift Testing suites silently dropped during large-suite splitting.

The weight calculation in TEST_TOKEN_RE counts both @Test and func test... tokens, but XCTEST_METHOD_RE only extracts XCTest methods. When a suite with ≥40 XCTest methods also contains @Test cases, the large-suite branch (lines 132-134) emits only XCTest selectors, silently dropping any Swift Testing tests.

Either prevent splitting for mixed suites or keep them at suite level:

🐛 Proposed guard for mixed Swift Testing suites
 TEST_TOKEN_RE = re.compile(r"(^|\s)(`@Test`\b|func\s+test[A-Za-z0-9_]*\s*\()")
+SWIFT_TESTING_RE = re.compile(r"(^|\s)`@Test`\b")
 XCTEST_METHOD_RE = re.compile(
     r"^\s*(?:(?:final|private|fileprivate|internal|public)\s+)*"
     r"func\s+(test[A-Za-z0-9_]*)\s*\("
 )

Then in discover_selectors, before splitting:

+            has_swift_testing = any(SWIFT_TESTING_RE.search(line) for line in body)
-            if len(methods) >= LARGE_SUITE_METHOD_THRESHOLD:
+            if len(methods) >= LARGE_SUITE_METHOD_THRESHOLD and not has_swift_testing:
                 selectors.extend(methods)
                 continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/cmux_unit_test_shard.py` around lines 14 - 25, The TEST_TOKEN_RE
regex counts both `@Test` and func test tokens when determining if a suite exceeds
LARGE_SUITE_METHOD_THRESHOLD, but XCTEST_METHOD_RE only extracts XCTest
selectors in the large-suite splitting logic around lines 132-134, silently
dropping Swift Testing `@Test` cases. Detect mixed suites that contain both `@Test`
annotations and XCTest methods by checking if the test count from TEST_TOKEN_RE
exceeds the count from XCTEST_METHOD_RE, and either skip the large-suite
splitting for these mixed suites to keep them at suite level or modify the
selector extraction logic to include both `@Test` and XCTest selectors when a
suite is mixed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@scripts/ci/cmux_unit_test_shard.py`:
- Around line 14-25: The TEST_TOKEN_RE regex counts both `@Test` and func test
tokens when determining if a suite exceeds LARGE_SUITE_METHOD_THRESHOLD, but
XCTEST_METHOD_RE only extracts XCTest selectors in the large-suite splitting
logic around lines 132-134, silently dropping Swift Testing `@Test` cases. Detect
mixed suites that contain both `@Test` annotations and XCTest methods by checking
if the test count from TEST_TOKEN_RE exceeds the count from XCTEST_METHOD_RE,
and either skip the large-suite splitting for these mixed suites to keep them at
suite level or modify the selector extraction logic to include both `@Test` and
XCTest selectors when a suite is mixed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d4d5ea6-75e5-4018-b77b-e77ea46526f0

📥 Commits

Reviewing files that changed from the base of the PR and between 229623d and 1a45023.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • scripts/ci/cmux_unit_test_shard.py
  • scripts/ci/run-in-console-session.sh
  • scripts/ci/xcodebuild_noninteractive.py
  • tests/test_ci_cmux_unit_test_shard.py
  • tests/test_ci_unit_test_spm_retry.sh
  • tests/test_ci_xcodebuild_noninteractive_helper.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1c14489. Configure here.

if selected_tests_result == "passed" or saw_passing_terminal_summary:
return 0
if selected_tests_result == "failed":
return POST_TEST_FAILED_EXIT_CODE

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Failed tests masked as success

Medium Severity

After the post-test grace timeout, exit handling treats ** TEST SUCCEEDED ** in the rolling output buffer as success before checking whether the matched Selected tests summary was failed. A stale success marker in the 4KB window can make a failed shard run return exit code 0, so CI may skip failure analysis when EXIT_CODE is zero.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c14489. Configure here.

Comment on lines +21 to +24
XCTEST_METHOD_RE = re.compile(
r"^\s*(?:(?:final|private|fileprivate|internal|public)\s+)*"
r"func\s+(test[A-Za-z0-9_]*)\s*\("
)

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 XCTEST_METHOD_RE silently drops override func test… methods from large-suite splits

XCTEST_METHOD_RE only allows final|private|fileprivate|internal|public as leading modifiers. A method like override func testFoo() or public override func testFoo() will not match, so it will never appear in declaration.methods. For small suites this is harmless because the whole-suite selector covers it, but when a suite crosses LARGE_SUITE_METHOD_THRESHOLD the code falls into selectors.extend(methods) without including those override methods — they get no -only-testing argument and are silently skipped across every shard.

Suggested change
XCTEST_METHOD_RE = re.compile(
r"^\s*(?:(?:final|private|fileprivate|internal|public)\s+)*"
r"func\s+(test[A-Za-z0-9_]*)\s*\("
)
XCTEST_METHOD_RE = re.compile(
r"^\s*(?:(?:final|open|override|private|fileprivate|internal|public)\s+)*"
r"func\s+(test[A-Za-z0-9_]*)\s*\("
)

@lawrencecchen
lawrencecchen merged commit 3d02ebe into main Jun 20, 2026
32 checks passed
@lawrencecchen
lawrencecchen deleted the feature-cut-cicd-time branch June 20, 2026 02:27
azooz2003-bit added a commit that referenced this pull request Jun 20, 2026
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) <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Jun 20, 2026
…s/splits) (#6519)

* ci: aggregate required gates so renames/splits stop desyncing branch protection

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) <noreply@anthropic.com>

* ci: rename the sharded job key tests -> app-host-unit-tests

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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 1c144899 Deployed Jun 20, 2026 by vercel[bot]
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