Skip to content

Commit d0538b3

Browse files
committed
ci: run real capture on Blacksmith macOS by default, hosted macOS as fallback
GitHub-hosted macOS allows 5 concurrent jobs for the whole org; Blacksmith macOS (M4 Pro VM, 6 vCPU, logged-in GUI session) has no cap. Blacksmith denies ScreenCaptureKit until TCC grants Screen Recording to the responsible process, so the job grants it (and Microphone) to the test binary, the stimulus, cargo and the runner ancestry, records replayd approvals and restarts tccd. It has no audio device: BlackHole 2ch becomes the default input and output and the stimulus's output, which makes system audio capturable, so system audio is required there and the system-only case runs. Dispatch runners=hosted or both keeps the hosted jobs.
1 parent 660c907 commit d0538b3

1 file changed

Lines changed: 71 additions & 3 deletions

File tree

‎.github/workflows/verify-recording.yml‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
name: Verify recording (real capture)
22

33
# Records a marker stimulus through Cap's real Studio and Instant pipelines
4-
# (ScreenCaptureKit + VideoToolbox) on hosted macOS runners and checks every
4+
# (ScreenCaptureKit + VideoToolbox) on macOS runners and checks every
55
# frame and audio marker against the host-clock truth. See
66
# crates/verify/tests/recording.rs.
7+
#
8+
# Runners: Blacksmith macOS (M4 Pro VM, 6 vCPU, logged-in GUI session, no
9+
# concurrency cap) by default; GitHub-hosted macOS (5 concurrent jobs for the
10+
# whole org) on dispatch with runners=hosted or both. Blacksmith needs Screen
11+
# Recording granted in TCC to the binaries that capture and their runner
12+
# ancestry, and an audio device (BlackHole), which also makes system audio
13+
# capturable there.
714

815
on:
916
workflow_dispatch:
@@ -21,6 +28,11 @@ on:
2128
description: "Preserve recordings and raw crash snapshots for passing cases"
2229
type: boolean
2330
default: false
31+
runners:
32+
description: "blacksmith (default), hosted (GitHub macOS, fallback) or both"
33+
type: choice
34+
options: [blacksmith, hosted, both]
35+
default: blacksmith
2436
push:
2537
branches:
2638
- ci/verify-recording
@@ -41,7 +53,7 @@ jobs:
4153
strategy:
4254
fail-fast: false
4355
matrix:
44-
runner: [macos-15, macos-26]
56+
runner: ${{ fromJSON(inputs.runners == 'hosted' && '["macos-15","macos-26"]' || inputs.runners == 'both' && '["blacksmith-6vcpu-macos-15","blacksmith-6vcpu-macos-26","macos-15","macos-26"]' || '["blacksmith-6vcpu-macos-15","blacksmith-6vcpu-macos-26"]') }}
4557
runs-on: ${{ matrix.runner }}
4658
timeout-minutes: 45
4759
env:
@@ -54,7 +66,13 @@ jobs:
5466
CAP_VERIFY_SCRATCH: ${{ github.workspace }}/verify-scratch
5567
CAP_VERIFY_REPORT_DIR: ${{ github.workspace }}/verify-report
5668
CAP_VERIFY_DUMP: ${{ github.workspace }}/verify-dump
57-
CAP_VERIFY_AUDIO_OUTPUT: ${{ matrix.runner == 'macos-26' && 'Null Audio Device' || '' }}
69+
# Hosted macos-26 has a virtual output device that gives
70+
# ScreenCaptureKit only silence, so system audio is not required
71+
# there; Blacksmith gets BlackHole (set up below), which carries the
72+
# stimulus's audio to ScreenCaptureKit, so system audio is required
73+
# and the system-only case runs.
74+
CAP_VERIFY_AUDIO_OUTPUT: ${{ startsWith(matrix.runner, 'blacksmith-') && 'BlackHole 2ch' || matrix.runner == 'macos-26' && 'Null Audio Device' || '' }}
75+
CAP_VERIFY_REQUIRE_SYSTEM_AUDIO: ${{ startsWith(matrix.runner, 'blacksmith-') && '1' || '' }}
5876
steps:
5977
- uses: actions/checkout@v4
6078

@@ -97,6 +115,56 @@ jobs:
97115
cargo test --locked -p cap-verify --test recording --no-run
98116
echo "build seconds: $(( $(date +%s) - start ))"
99117
118+
# Blacksmith runs a logged-in GUI session with SIP disabled, but
119+
# ScreenCaptureKit is denied until TCC grants the responsible process
120+
# (Runner.Worker), so grant Screen Recording (and Microphone) to every
121+
# binary that captures plus the whole runner ancestry, then restart
122+
# tccd. It has no audio device: BlackHole provides input and output.
123+
- name: Blacksmith capture permissions and audio device
124+
if: startsWith(matrix.runner, 'blacksmith-')
125+
run: |
126+
set -euo pipefail
127+
brew install --cask blackhole-2ch
128+
brew install switchaudio-osx
129+
sudo killall coreaudiod || true
130+
for i in $(seq 1 30); do
131+
SwitchAudioSource -a -t output | grep -qx "BlackHole 2ch" && break
132+
sleep 1
133+
done
134+
SwitchAudioSource -s "BlackHole 2ch" -t output
135+
SwitchAudioSource -s "BlackHole 2ch" -t input
136+
echo "outputs:"; SwitchAudioSource -a -t output
137+
echo "default output: $(SwitchAudioSource -c -t output)"
138+
139+
cargo test --locked -p cap-verify --test recording --no-run --message-format=json 2>/dev/null \
140+
| python3 -c 'import json, sys
141+
for line in sys.stdin:
142+
m = json.loads(line)
143+
if m.get("reason") == "compiler-artifact" and m.get("executable") and m["target"]["name"] in ("recording", "cap-verify-stimulus"):
144+
print(m["executable"])' > "$RUNNER_TEMP/capture-binaries"
145+
test "$(wc -l < "$RUNNER_TEMP/capture-binaries")" -ge 2
146+
p=$$
147+
while [ "$p" -gt 1 ]; do
148+
ps -o comm= -p "$p"
149+
p=$(ps -o ppid= -p "$p" | tr -d ' ')
150+
done >> "$RUNNER_TEMP/capture-binaries"
151+
for c in "$(rustup which cargo)" "$HOME/.cargo/bin/cargo"; do echo "$c"; done >> "$RUNNER_TEMP/capture-binaries"
152+
153+
db="/Library/Application Support/com.apple.TCC/TCC.db"
154+
approvals="$HOME/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist"
155+
mkdir -p "$(dirname "$approvals")"
156+
now=$(date +%s)
157+
until=$(date -u -v+1y '+%a %b %d %H:%M:%S GMT %Y')
158+
sort -u "$RUNNER_TEMP/capture-binaries" | grep '^/' | while read -r client; do
159+
for service in kTCCServiceScreenCapture kTCCServiceMicrophone; do
160+
sudo sqlite3 "$db" "INSERT OR REPLACE INTO access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier,flags,last_modified) VALUES ('$service','$client',1,2,4,1,'UNUSED',0,$now);"
161+
done
162+
/usr/libexec/PlistBuddy -c "Add :$client date $until" "$approvals" >/dev/null 2>&1 || true
163+
echo "granted $client"
164+
done
165+
sudo killall tccd || true
166+
sleep 2
167+
100168
- name: Real-capture recording oracle
101169
run: |
102170
mkdir -p "$CAP_VERIFY_DUMP"

0 commit comments

Comments
 (0)