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
6 changes: 6 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
self-hosted-runner:
labels:
# Active default lives in the MACOS_RUNNER_15 / MACOS_RUNNER_26 repo
# variables; these are the literal labels referenced as fallbacks or as
# manual workflow_dispatch choices. See docs/macos-ci-runners.md.
- blacksmith-6vcpu-macos-15
- blacksmith-6vcpu-macos-26
- blacksmith-6vcpu-macos-latest
- warp-macos-15-arm64-6x
- warp-macos-26-arm64-6x
- depot-macos-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-ghosttykit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ concurrency:

jobs:
build-ghosttykit:
runs-on: warp-macos-15-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 20
env:
GHOSTTYKIT_CRASH_REPORT_SUBDIR: cmux/crash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci-macos-compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ jobs:
fail-fast: false
matrix:
include:
- os: warp-macos-15-arm64-6x
- os: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout: 30
startup_smoke: true
virtual_display: true
skip_zig: false
- os: warp-macos-26-arm64-6x
- os: ${{ vars.MACOS_RUNNER_26 || 'warp-macos-26-arm64-6x' }}
timeout: 30
startup_smoke: true
virtual_display: false
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ jobs:
bun test tests/vm-db-read-model.test.ts

tests:
runs-on: warp-macos-15-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 75
env:
CMUX_SKIP_ZIG_BUILD: "1"
Expand Down Expand Up @@ -391,7 +391,7 @@ jobs:
# Keep lag validation separate from UI regressions so functional UI failures
# and performance regressions stay isolated. Broader interactive UI suites
# still run via test-e2e.yml on GitHub-hosted runners.
runs-on: warp-macos-15-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 20
steps:
- name: Checkout
Expand Down Expand Up @@ -584,7 +584,7 @@ jobs:
# Compile the same unsigned universal Release app that nightly builds before
# signing, notarization, and publishing. This catches DEBUG/Release boundary
# mistakes before they reach main.
runs-on: warp-macos-26-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_26 || 'warp-macos-26-arm64-6x' }}
timeout-minutes: 20
steps:
- name: Checkout
Expand Down Expand Up @@ -688,7 +688,7 @@ jobs:
lipo "$HELPER_BINARY" -verify_arch arm64 x86_64

ui-regressions:
runs-on: warp-macos-15-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 25
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
build-sign-notarize-nightly:
needs: decide
if: needs.decide.outputs.should_build == 'true'
runs-on: warp-macos-26-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_26 || 'warp-macos-26-arm64-6x' }}
timeout-minutes: 20
steps:
- name: Checkout build ref
Expand Down
33 changes: 21 additions & 12 deletions .github/workflows/perf-activation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ on:
required: false
default: ""
runner:
description: macOS runner
description: macOS runner (auto follows the MACOS_RUNNER_15 repo variable, then warp)
required: false
default: warp-macos-15-arm64-6x
default: auto
type: choice
options:
- auto
- blacksmith-6vcpu-macos-15
- blacksmith-6vcpu-macos-26
- blacksmith-6vcpu-macos-latest
- warp-macos-15-arm64-6x
- depot-macos-latest
- warp-macos-26-arm64-6x
workspace_count:
description: Fixture workspace count
required: false
Expand All @@ -35,7 +39,7 @@ concurrency:

jobs:
activation-session:
runs-on: ${{ inputs.runner || 'warp-macos-15-arm64-6x' }}
runs-on: ${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}
timeout-minutes: 45
env:
PERF_TAG: perfci
Expand Down Expand Up @@ -90,8 +94,8 @@ jobs:
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .ci-source-packages
key: spm-${{ hashFiles('cmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
restore-keys: spm-
key: spm-${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}-${{ hashFiles('cmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
restore-keys: spm-${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}-

- name: Resolve Swift packages
run: |
Expand Down Expand Up @@ -138,12 +142,17 @@ jobs:
run: |
set -euo pipefail
APP_PATH="$HOME/Library/Developer/Xcode/DerivedData/cmux-$PERF_TAG/Build/Products/Debug/cmux DEV $PERF_TAG.app"
swift scripts/bench-window-visibility.swift \
"$APP_PATH" \
"com.cmuxterm.app.debug.$PERF_TAG" \
30 \
--cmd-tab-activation \
--cg-visibility \
# The GitHub Actions runner process runs in launchd's system
# bootstrap, not the console user's Aqua session, so windows
# created by apps launched via NSWorkspace.openApplication do
# not show up in CGWindowListCopyWindowInfo([.optionOnScreenOnly]).
# Re-enter the Aqua session via launchctl asuser before running
# the bench so visibility polling sees real on-screen windows.
CONSOLE_USER="$(stat -f %Su /dev/console)"
CONSOLE_UID="$(id -u "$CONSOLE_USER")"
sudo -n launchctl asuser "$CONSOLE_UID" sudo -n -u "$CONSOLE_USER" -E \
env PATH="$PATH" DEVELOPER_DIR="$DEVELOPER_DIR" \
bash -c "cd '$PWD' && swift scripts/bench-window-visibility.swift '$APP_PATH' 'com.cmuxterm.app.debug.$PERF_TAG' 30 --cmd-tab-activation --cg-visibility" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unconditional sudo with no fallback unlike test-e2e pattern

Low Severity

The launchctl asuser wrapper unconditionally requires sudo -n without first checking availability, unlike the robust pattern in test-e2e.yml which guards with if sudo -n true 2>/dev/null and falls back to direct execution with a warning. The stat -f %Su /dev/console call also lacks 2>/dev/null || true error suppression and the $CONSOLE_USER != "root" safety check. Since perf-activation.yml triggers on pull_request and can fall back to WarpBuild runners (when vars.MACOS_RUNNER_15 is unset), this step will hard-fail on any runner without passwordless sudo, where the old direct swift invocation would have succeeded.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 316e73b. Configure here.

> perf-results/cmd-tab-activation.txt
Comment thread
coderabbitai[bot] marked this conversation as resolved.
cat perf-results/cmd-tab-activation.txt

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:

jobs:
build-sign-notarize:
runs-on: warp-macos-26-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_26 || 'warp-macos-26-arm64-6x' }}
timeout-minutes: 20
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-depot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ on:

jobs:
tests:
runs-on: warp-macos-15-arm64-6x
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 20
steps:
- name: Checkout
Expand Down
24 changes: 15 additions & 9 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: E2E test with video recording
run-name: ${{ inputs.test_filter }} on ${{ inputs.runner || 'depot-macos-latest' }} @ ${{ inputs.ref || github.ref_name }}
run-name: ${{ inputs.test_filter }} on ${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }} @ ${{ inputs.ref || github.ref_name }}

on:
workflow_dispatch:
Expand All @@ -21,29 +21,35 @@ on:
default: true
type: boolean
runner:
description: "Runner OS (Depot runners for GUI activation support)"
description: "Runner OS (auto follows the MACOS_RUNNER_15 repo variable, then warp; pick depot-macos-* for GUI activation)"
required: false
default: "depot-macos-latest"
default: "auto"
type: choice
options:
- auto
- blacksmith-6vcpu-macos-15
- blacksmith-6vcpu-macos-26
- blacksmith-6vcpu-macos-latest
- warp-macos-15-arm64-6x
- warp-macos-26-arm64-6x
- depot-macos-latest
- depot-macos-14

concurrency:
group: e2e-${{ inputs.runner || 'depot-macos-latest' }}-${{ inputs.ref || github.ref_name }}-${{ inputs.test_filter }}
group: e2e-${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}-${{ inputs.ref || github.ref_name }}-${{ inputs.test_filter }}
cancel-in-progress: true

jobs:
e2e:
runs-on: ${{ inputs.runner || 'depot-macos-latest' }}
runs-on: ${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}
timeout-minutes: 20
env:
TEST_REF: ${{ inputs.ref || github.ref }}
steps:
- name: Validate Depot runner identity
if: ${{ startsWith(inputs.runner || 'depot-macos-latest', 'depot-macos-') }}
if: ${{ startsWith((!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner, 'depot-macos-') }}
env:
REQUESTED_RUNNER: ${{ inputs.runner || 'depot-macos-latest' }}
REQUESTED_RUNNER: ${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}
RUNNER_CONTEXT_NAME: ${{ runner.name }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -226,8 +232,8 @@ jobs:
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .ci-source-packages
key: spm-${{ inputs.runner || 'depot-macos-latest' }}-${{ hashFiles('cmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
restore-keys: spm-${{ inputs.runner || 'depot-macos-latest' }}-
key: spm-${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}-${{ hashFiles('cmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
restore-keys: spm-${{ (!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner }}-

- name: Resolve Swift packages
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tmux-corpus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:

terminal-nightly:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: [self-hosted, warp-macos-15-arm64-6x]
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
timeout-minutes: 30
steps:
- name: Checkout
Expand Down
42 changes: 42 additions & 0 deletions docs/macos-ci-runners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# macOS CI runners

All paid macOS CI/CD jobs pick their runner from two repository variables instead of a hardcoded label:

- `MACOS_RUNNER_15` for macOS 15 jobs (most jobs, plus the e2e/perf defaults)
- `MACOS_RUNNER_26` for macOS 26 jobs (release, nightly, the `release-build` job, compat)

Workflows reference them as `runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}`. If a variable is unset, the job falls back to WarpBuild, so CI is never broken by a missing variable.

## Switch Blacksmith <-> WarpBuild

The switch is a repo-variable change. It takes effect on the next workflow run, with no PR or commit.

Use Blacksmith (default):

```bash
gh variable set MACOS_RUNNER_15 --repo manaflow-ai/cmux -b blacksmith-6vcpu-macos-15
gh variable set MACOS_RUNNER_26 --repo manaflow-ai/cmux -b blacksmith-6vcpu-macos-26
```

Fall back to WarpBuild (e.g. Blacksmith macOS capacity is queuing, as happened in https://github.com/manaflow-ai/cmux/pull/4926):

```bash
gh variable delete MACOS_RUNNER_15 --repo manaflow-ai/cmux
gh variable delete MACOS_RUNNER_26 --repo manaflow-ai/cmux
```

Deleting the variables reverts to the WarpBuild fallback baked into the workflows. You can also set them explicitly to `warp-macos-15-arm64-6x` / `warp-macos-26-arm64-6x`.

Check current values:

```bash
gh variable list --repo manaflow-ai/cmux
```

## Manual runs

`perf-activation.yml` and `test-e2e.yml` keep a `runner` choice input that defaults to `auto`. `auto` (and the empty `pull_request` case for perf) follows `MACOS_RUNNER_15` then the Warp fallback, so flipping the repo variable also redirects these workflows. An explicit choice wins over the variable; both dropdowns expose `warp-macos-15-arm64-6x` / `warp-macos-26-arm64-6x` so an operator can pick Warp directly during a Blacksmith outage. `test-e2e.yml` also keeps `depot-macos-*` choices and a Depot identity guard for GUI-activation runs.

## Guard

`tests/test_ci_self_hosted_guard.sh` (run by the `workflow-guard-tests` job) asserts every paid macOS job references `vars.MACOS_RUNNER_*` or a Blacksmith/Warp label, so a job can never silently fall back to a free GitHub-hosted runner. Keep new labels in `.github/actionlint.yaml`.
35 changes: 19 additions & 16 deletions tests/test_ci_self_hosted_guard.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
# Regression test for https://github.com/manaflow-ai/cmux/issues/385.
# Ensures paid CI jobs use WarpBuild runners.
# Ensures paid CI jobs use a paid macOS runner (Blacksmith or WarpBuild, routed
# through the MACOS_RUNNER_15 / MACOS_RUNNER_26 repo variables), never a free
# GitHub-hosted runner. Flip Blacksmith<->Warp by editing those repo variables;
# see docs/macos-ci-runners.md.
# Fork PRs are gated by GitHub's built-in "Require approval for outside
# collaborators" setting, so workflow-level fork guards are not needed.
set -euo pipefail
Expand All @@ -11,19 +14,19 @@ GHOSTTYKIT_FILE="$ROOT_DIR/.github/workflows/build-ghosttykit.yml"
COMPAT_FILE="$ROOT_DIR/.github/workflows/ci-macos-compat.yml"
E2E_FILE="$ROOT_DIR/.github/workflows/test-e2e.yml"

check_warp_runner() {
check_macos_runner() {
local file="$1" job="$2"
if ! awk -v job="$job" '
$0 ~ "^ "job":" { in_job=1; next }
in_job && /^ [^[:space:]]/ { in_job=0 }
in_job && /runs-on:.*warp-macos-.*-arm64/ { saw_warp=1 }
in_job && /os: warp-macos-.*-arm64/ { saw_warp=1 }
END { exit !(saw_warp) }
in_job && /^ [^[:space:]#][^:]*:[[:space:]]*(#.*)?$/ { in_job=0 }
in_job && /runs-on:.*(vars\.MACOS_RUNNER|blacksmith-[0-9]+vcpu-macos-|warp-macos-[0-9]+-arm64)/ { saw=1 }
in_job && /os:.*(vars\.MACOS_RUNNER|blacksmith-[0-9]+vcpu-macos-|warp-macos-[0-9]+-arm64)/ { saw=1 }
END { exit !(saw) }
' "$file"; then
echo "FAIL: $job in $(basename "$file") must use a WarpBuild runner"
echo "FAIL: $job in $(basename "$file") must run on a paid macOS runner (vars.MACOS_RUNNER_* or a Blacksmith/Warp label), not a GitHub-hosted runner"
exit 1
fi
echo "PASS: $job WarpBuild runner is present"
echo "PASS: $job in $(basename "$file") uses a paid macOS runner"
}

check_e2e_runner_fallbacks() {
Expand Down Expand Up @@ -58,7 +61,7 @@ check_e2e_runner_fallbacks() {
exit 1
fi

if ! grep -Fq "startsWith(inputs.runner || 'depot-macos-latest', 'depot-macos-')" "$E2E_FILE"; then
if ! grep -Fq "startsWith((!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x') || inputs.runner, 'depot-macos-')" "$E2E_FILE"; then
echo "FAIL: test-e2e.yml must validate all Depot macOS runner choices"
exit 1
fi
Expand Down Expand Up @@ -121,16 +124,16 @@ check_release_build_signal() {
}

# ci.yml jobs
check_warp_runner "$CI_FILE" "tests"
check_warp_runner "$CI_FILE" "tests-build-and-lag"
check_warp_runner "$CI_FILE" "release-build"
check_warp_runner "$CI_FILE" "ui-regressions"
check_macos_runner "$CI_FILE" "tests"
check_macos_runner "$CI_FILE" "tests-build-and-lag"
check_macos_runner "$CI_FILE" "release-build"
check_macos_runner "$CI_FILE" "ui-regressions"

# build-ghosttykit.yml
check_warp_runner "$GHOSTTYKIT_FILE" "build-ghosttykit"
check_macos_runner "$GHOSTTYKIT_FILE" "build-ghosttykit"

# ci-macos-compat.yml (uses matrix.os with WarpBuild runners)
check_warp_runner "$COMPAT_FILE" "compat-tests"
# ci-macos-compat.yml (matrix.os routed through the MACOS_RUNNER_* repo vars)
check_macos_runner "$COMPAT_FILE" "compat-tests"

# test-e2e.yml is manual, so keep the Depot GUI runner choices but cancel
# duplicate queued runs for the same ref/filter/runner.
Expand Down
Loading