Skip to content

Add Blacksmith iOS video recording mode - #6479

Closed
lawrencecchen wants to merge 22 commits into
mainfrom
feat-ios-video-recording
Closed

lawrencecchen wants to merge 22 commits into
mainfrom
feat-ios-video-recording

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Adds an ios-video mode to the existing reload-build.yml workflow.

The mode boots an iOS Simulator on a Blacksmith macOS runner, builds cmux for testing, starts xcrun simctl io recordVideo, runs a selected XCUITest to drive the app, stops the recorder, and uploads the mp4 plus logs/metadata.

Verified with local hq dispatcher:


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Mostly dispatch-only CI, but it changes DEBUG mobile launch/pairing behavior and runs privileged TCC DB writes on macOS runners during sync-video.

Overview
Extends reload-build with ios-video and sync-video platform choices, plus test_filter and device_family inputs for simulator-driven recordings.

ios-video boots a simulator, runs a selected UI test while simctl recordVideo captures H.264, and uploads MP4, logs, xcresult, and metadata.json (failing the job if no tests ran). sync-video grants screen-capture TCC for ffmpeg, then runs new scripts/ci/record-real-sync-video.sh to build tagged macOS and iOS apps, mint an attach URL, auto-launch iOS with fixture auth, record Mac frames via debug screenshots and iOS via simctl, and stitch a side-by-side demo MP4. Artifact upload now runs always() so partial logs survive failures.

On iOS DEBUG, CMUX_DOGFOOD_ATTACH_URL can be read from env, --cmux-dogfood-attach-url, or UserDefaults (with tests for precedence). connectUITestAttachURLIfNeeded no longer requires Stack sign-in for raw attach tickets—it uses the same attach-ticket path as .onOpenURL—and runs from onAppear before stored-Mac reconnect, with OSLog around attach handling.

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


Summary by cubic

Adds ios-video and sync-video modes to reload-build.yml to record iOS simulator runs and synchronized desktop+iOS demos. Also auto‑opens the selected workspace on iOS after attach and improves attach URL sourcing (env, --cmux-dogfood-attach-url, or UserDefaults) for reliable cloud pairing.

  • New Features

    • ios-video: records H.264 via simctl io recordVideo while a selected UI test runs; uploads MP4, screenshot, logs, xcresult, and metadata.json (includes test exit code). Inputs: test_filter, device_family.
    • sync-video: provisions GhosttyKit, grants TCC for ffmpeg/screencapture, builds tagged macOS+iOS apps (iOS with --no-launch), mints an attach URL, seeds it into iOS defaults/launch args/env, auto‑attaches, opens the selected workspace on iOS, records macOS via terminal snapshots and iOS via simctl, then stitches a 24s 1080p side‑by‑side MP4 with scripts/ci/record-real-sync-video.sh.
    • DEBUG iOS attach: read CMUX_DOGFOOD_ATTACH_URL from env, launch arg --cmux-dogfood-attach-url, or UserDefaults; launch‑time raw tickets go through the same connectAttachURL path as .onOpenURL, bypassing Stack sign‑in, with added attach‑flow logging.
  • Bug Fixes

    • Safer simulator lifecycle and artifact retention: erase/shutdown, delete created devices, guarded bootstatus, artifact upload under always().
    • More reliable sync recording: portable/bounded timeouts, quieter CLI JSON parsing, canonical workspace creation.

Written for commit 0af62f1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Chores
    • Enhanced CI/CD testing with real-time video capture for iOS simulator runs, including newly supported synchronized desktop + iOS recording mode.
    • Added manual run controls to choose the recording platform, target device family (iPhone/iPad), and UI test filter.
    • Automatically records, validates, and uploads the resulting video artifacts along with metadata for easier debugging when tests run.

@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 23, 2026 1:48pm
cmux-staging Building Building Preview, Comment Jun 23, 2026 1:48pm

@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

Two new CI recording platforms—ios-video and sync-video—are added to the reload-build.yml workflow via new dispatch inputs (test_filter, device_family). The workflow gains steps that provision iOS simulators, run filtered UI tests with video capture, and stitch results. A new script scripts/ci/record-real-sync-video.sh implements the full synchronized macOS+iOS recording pipeline with ffmpeg overlay composition.

Changes

iOS and Sync-Video Recording Workflow

Layer / File(s) Summary
Dispatch inputs, platform options, and artifact upload condition
.github/workflows/reload-build.yml
Adds ios-video and sync-video to the platform choice list, introduces test_filter (default iOS UI test target) and device_family (default iphone, choices iphone/ipad) dispatch inputs, extends the GhosttyKit provisioning condition to include sync-video, and sets the artifact upload step to always().
ios-video and sync-video workflow steps
.github/workflows/reload-build.yml
Inserts the sync-video TCC screen-recording grant and ffmpeg availability check before invoking the shell script; inserts the full ios-video step that downloads the iOS platform if absent, selects or creates an appropriate simulator via embedded Python using xcrun simctl, boots it with dark appearance, builds and runs filtered UI tests via xcodebuild, records video via simctl io recordVideo, captures .xcresult bundle and screenshot, performs simulator cleanup (including deletion if newly created), validates video output exists, writes artifact/metadata.json with test exit code, and forces exit code 65 when no matching tests are executed successfully.
record-real-sync-video.sh: setup, cleanup, and helper functions
scripts/ci/record-real-sync-video.sh
Defines strict Bash mode with required BUILD_TAG, sets defaults for DEVICE_FAMILY, ARTIFACT_DIR, and SYNC_MARKER, computes all output/log/socket/metadata paths, registers a trap-based cleanup function that stops recorders and quits related processes, and implements require_ffmpeg (Homebrew install if missing), select_simulator (embedded Python querying xcrun simctl devices/runtimes), wait_for_socket, cmux_tagged wrapper, json_field extractor, mint_attach_url (RPC with Node helper), start_macos_recording, start_ios_recording, stop_recorders, and stitch_videos (ffmpeg trim/scale/overlay composition).
record-real-sync-video.sh: main orchestration flow
scripts/ci/record-real-sync-video.sh
Enforces iOS platform readiness via download if needed, boots the simulator with dark appearance, enables macOS iOS pairing, reloads/builds the tagged macOS cmux app, creates a cmux workspace/surface, mints the terminal-scoped attach URL via RPC ticket, launches the iOS app and opens the attach URL, starts both macOS (ffmpeg AVFoundation) and iOS (simctl) recorders, sends ordered surface messages (clear, desktop text, sync marker), captures evidence (read-screen dump and macOS/iOS screenshots), stops recorders, stitches a 1920×1080 combined MP4 via ffmpeg overlay/trim filter, and writes metadata.json with tag/platform/device/simulator/workspace/surface IDs and output video basename.

Sequence Diagram(s)

sequenceDiagram
  actor Trigger as Workflow Dispatch
  participant Workflow as reload-build.yml
  participant Simulator as iOS Simulator
  participant cmuxMacOS as cmux macOS debug CLI
  participant ffmpeg as ffmpeg / simctl recordVideo
  participant Artifact as artifact/

  Trigger->>Workflow: platform=sync-video or ios-video, device_family, test_filter

  alt ios-video
    Workflow->>Simulator: select or create simulator (Python + xcrun simctl)
    Workflow->>Simulator: boot + dark appearance
    Workflow->>Workflow: xcodebuild build-for-testing
    Workflow->>Simulator: simctl io recordVideo (background)
    Workflow->>Workflow: xcodebuild test-without-building (test_filter)
    Workflow->>Artifact: .xcresult + screenshot.png + metadata.json
  else sync-video
    Workflow->>Workflow: grant TCC + ensure ffmpeg
    Workflow->>cmuxMacOS: enable iOS pairing + reload macOS side
    Workflow->>Simulator: boot + launch iOS debug bundle
    cmuxMacOS->>cmuxMacOS: create workspace/surface + mint attach URL
    Workflow->>Simulator: open attach URL
    Workflow->>ffmpeg: start macOS screen capture
    Workflow->>Simulator: simctl io recordVideo
    cmuxMacOS->>cmuxMacOS: send clear / desktop text / sync marker
    Workflow->>ffmpeg: SIGINT stop recorders
    ffmpeg->>Artifact: macOS.mp4 + ios.mp4
    Workflow->>ffmpeg: stitch 1920x1080 combined MP4
    Workflow->>Artifact: metadata.json + stitched video
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#6354: Modifies the same reload-build.yml by extending workflow_dispatch platform handling and iOS build provisioning (GhosttyKit), forming the base on which this PR adds the ios-video and sync-video recording steps.

Poem

🐇 Hop, hop—the camera rolls,
A simulator wakes and strolls,
ffmpeg stitches, side by side,
macOS and iOS unified.
With sync markers sent across the wire,
The rabbit films what hearts desire! 🎬


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 2 warnings)

Check name Status Explanation Resolution
Cmux No Hacky Sleeps ❌ Error record-real-sync-video.sh violates runtime-no-hacky-sleeps.md with 8+ sleeps polling socket/filesystem, process startup, RPC, log files, and command readiness instead of using event-based mechanisms. Replace polling sleeps with filesystem events, process signals, RPC acknowledgment, and proper command completion detection in wait_for_socket, mint_attach_url, start_macos/ios_recording, workspace readiness, and app launch sequences.
Cmux Swiftpm Lockfiles ❌ Error 48 out of 60 Package.swift files lack corresponding Package.resolved lockfiles. Per swiftpm-package-resolved.md, cmux-owned Package.swift changes must include matching package-local Package.resolve... Add Package.resolved for all 48 packages missing lockfiles: CMUXAuthCore, CMUXMobileCore, CmuxAgentChat, CmuxSyncStore, CmuxAgentChatUI, CmuxMobileAnalytics through CmuxMobileWorkspace, CMUXAgentLaunch through CmuxSidebarProviderKit, and...
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.
Description check ⚠️ Warning PR description is incomplete and does not follow the required template structure. Add required sections: Summary (clear what changed and why), Testing (how tested and verified), Demo Video (with link or attachment), Review Trigger, and Checklist items. These are missing from the provided description.
✅ Passed checks (19 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Blacksmith iOS video recording mode' clearly describes the primary change: adding an iOS video recording capability to the workflow.
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 Swift source code changes—only CI workflow YAML and Bash scripts are modified. Swift actor isolation check is not applicable to non-Swift files.
Cmux Swift Blocking Runtime ✅ Passed PR modifies only CI/workflow YAML and Bash scripts; no production Swift code changes are present, making the Swift blocking runtime check not applicable.
Cmux Expensive Synchronous Load ✅ Passed Check not applicable: PR contains only CI workflow (YAML) and Bash script changes, no production Swift code modifications.
Cmux Cache Substitution Correctness ✅ Passed The PR modifies only YAML workflow configuration (.github/workflows/reload-build.yml) and a Bash shell script (scripts/ci/record-real-sync-video.sh). The cache substitution check applies only to pr...
Cmux Algorithmic Complexity ✅ Passed The PR changes only affect CI/DevOps code (GitHub workflow YAML and Bash CI scripts), which are explicitly out of scope for the algorithmic-complexity rule. The rule only applies to "production cod...
Cmux Swift Concurrency ✅ Passed PR modifies only CI infrastructure files (.github/workflows/reload-build.yml and scripts/ci/record-real-sync-video.sh), not cmux-owned Swift code. Check is not applicable.
Cmux Swift @Concurrent ✅ Passed PR contains no Swift files (.swift). Changes are limited to YAML workflow and bash script files, making the Swift @concurrent check inapplicable.
Cmux Swift File And Package Boundaries ✅ Passed PR only modifies CI infrastructure (YAML workflow and Bash script), not production Swift code. Check for Swift file/package boundaries is not applicable.
Cmux Swift Logging ✅ Passed PR modifies only CI workflow YAML and bash scripts; contains no production Swift code changes. The custom check applies exclusively to production Swift changes.
Cmux User-Facing Error Privacy ✅ Passed Changes are confined to CI workflow (.github/workflows/) and CI scripts (scripts/ci/), which are developer-only operational infrastructure not shown to end users, qualifying as allowed cases per th...
Cmux Full Internationalization ✅ Passed PR modifies only CI workflow and scripts (operational code not shown to end users), with no production app source code changes. The internationalization rule explicitly applies only to "production...
Cmux Swiftui State Layout ✅ Passed PR contains only CI workflow (YAML) and shell script changes; no SwiftUI code modifications, so the swiftui-state-layout check is not applicable.
Cmux Architecture Rethink ✅ Passed PR modifies only CI configuration (YAML workflow) and Bash scripts; contains zero Swift code changes, making the swift-architectural-rethink rule inapplicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no Swift changes; only CI workflow and bash script modifications. The window close shortcut rule applies only to Swift window code (NSWindow, NSPanel, NSWindowController, SwiftUI Window...
Cmux Source Artifacts ✅ Passed reload-build.yml and record-real-sync-video.sh are legitimate hand-written source files (YAML workflow and Bash script). They reference artifact paths via variables, not commit binary artifacts, fo...
Cmux No Test Or Debug Seam In Production Source ✅ Passed Check applies only to production Swift source (*/Sources/**). PR modifies only .github/workflows/reload-build.yml (YAML) and scripts/ci/record-real-sync-video.sh (Bash), not production Swift source.
✨ 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 feat-ios-video-recording

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.

rm -rf "$RESULT_BUNDLE" "$VIDEO_PATH" "$SCREENSHOT_PATH"

xcrun simctl shutdown "$SIMULATOR_ID" >/dev/null 2>&1 || true
xcrun simctl erase "$SIMULATOR_ID"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Erase wipes shared simulators

Medium Severity

The ios-video step picks an existing available simulator when a preferred device already exists, then runs xcrun simctl erase on that UDID. On persistent self-hosted runners, that wipes a shared simulator other jobs rely on, not only a dedicated cmux Video instance.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4d97f30. Configure here.

"tag": "$BUILD_TAG",
"platform": "ios-video",
"runner": "${{ inputs.runner }}",
"sourceRef": "${{ inputs.ref }}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metadata omits resolved source ref

Low Severity

metadata.json sets sourceRef from ${{ inputs.ref }} only. When the dispatch leaves ref empty, checkout still uses inputs.ref || github.ref, but metadata records an empty sourceRef instead of the ref that was actually built.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4d97f30. Configure here.

} >> "$GITHUB_STEP_SUMMARY"

- name: Upload artifact
if: ${{ always() }}

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 builds upload partial artifacts

Low Severity

Adding if: ${{ always() }} to the upload step makes failed macos or ios runs publish an artifact directory that often contains only timings.json, not app.zip or archive.zip, which can look like a successful dev build to download logic that only checks for an artifact.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4d97f30. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds ios-video and sync-video recording modes to reload-build.yml, along with a new scripts/ci/record-real-sync-video.sh script that builds real macOS and iOS apps, launches them, auto-pairs, records both screens with ffmpeg and simctl io recordVideo, and stitches a side-by-side 1080p MP4. The Swift changes extend UITestConfig.dogfoodAttachURL to read from CLI arguments and UserDefaults, and update CMUXMobileRootView so raw attach tickets bypass the isAuthenticated guard (routing them through the existing .onOpenURL path).

  • reload-build.yml: Adds ios-video (inline shell) and sync-video (delegates to record-real-sync-video.sh) modes, new test_filter/device_family inputs, and makes artifact upload unconditional via always().
  • record-real-sync-video.sh: 504-line script handling simulator lifecycle, multi-source attach-URL seeding, TCC-bypassed ffmpeg screen capture, frame stitching, and metadata emission; uses sleep 18 for iOS app attachment (already flagged) and sleep 1 for log-stream startup (new finding).
  • UITestConfig / CMUXMobileRootView: All new behavior is inside #if DEBUG guards; tests cover the new argument and UserDefaults sources with isolated suites.

Confidence Score: 4/5

Safe to merge with one runtime fix needed in record-real-sync-video.sh.

The only new finding is start_ios_log_capture using a fixed 1-second sleep to assume xcrun simctl spawn ... log stream has started, with no liveness or output check. On a loaded runner this silently produces an empty log artifact. The Swift changes are well-contained behind #if DEBUG guards and the UITestConfig overloads have real production callers. The rest of the CI workflow changes look correct.

scripts/ci/record-real-sync-video.sh — the start_ios_log_capture function needs a bounded readiness poll instead of the fixed sleep.

Important Files Changed

Filename Overview
scripts/ci/record-real-sync-video.sh New 504-line CI script that builds, launches, records, and stitches macOS + iOS demo video. Uses sleep 18 (already flagged) for iOS app attachment readiness and sleep 1 in start_ios_log_capture (new finding) for log-stream startup readiness — both are fixed sleeps over process lifecycle with no real signal.
.github/workflows/reload-build.yml Adds ios-video and sync-video platform modes, new test_filter/device_family inputs, uploads artifacts under always(). The ios-video inline shell step correctly uses a log-polling loop for recorder readiness; previously flagged issues (BUILD_TAG in paths, JSON injection) are the main concerns.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift Extends DEBUG-only connectUITestAttachURLIfNeeded to handle raw attach tickets before authentication, adds OSLog-based logging to the attach flow, and skips stored-Mac reconnect when a launch attach URL is consumed. All new behavior is inside #if DEBUG or uses proper unified logging APIs.
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swift Adds CLI argument (--cmux-dogfood-attach-url) and UserDefaults sources for dogfood attach URL, all inside #if DEBUG. The internal dogfoodAttachURL(from:arguments:defaults:) overload has real production callers (via .standard), so it is not a pure test seam.
Packages/iOS/CmuxMobileSupport/Tests/CmuxMobileSupportTests/UITestConfigTests.swift Adds well-structured tests for the new argument and UserDefaults attach-URL sources; uses isolated UserDefaults suites to avoid cross-test pollution.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant WF as reload-build.yml
    participant SH as record-real-sync-video.sh
    participant SIM as iOS Simulator
    participant MAC as Tagged macOS cmux
    participant IOS as iOS cmux app

    WF->>SH: invoke (sync-video mode)
    SH->>SIM: boot simulator
    SH->>MAC: build + launch tagged macOS app
    MAC-->>SH: socket ready (wait_for_socket)
    SH->>MAC: workspace create (cmux_tagged)
    SH->>MAC: mint attach URL (mobile.attach_ticket.create)
    SH->>IOS: build + install (ios/scripts/reload.sh --no-launch)
    SH->>SIM: seed CMUX_DOGFOOD_ATTACH_URL into UserDefaults/launchctl
    SH->>SIM: launch iOS app (simctl launch)
    Note over SH,IOS: sleep 18 (fixed wait for attachment)
    SH->>SH: start_macos_recording + start_ios_recording
    SH->>MAC: send terminal keystrokes (cmux_tagged send)
    IOS-->>SIM: mirrors terminal content
    SH->>SH: stop_recorders
    SH->>SH: stitch_videos (ffmpeg side-by-side)
    SH->>WF: metadata.json + MP4 artifacts
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant WF as reload-build.yml
    participant SH as record-real-sync-video.sh
    participant SIM as iOS Simulator
    participant MAC as Tagged macOS cmux
    participant IOS as iOS cmux app

    WF->>SH: invoke (sync-video mode)
    SH->>SIM: boot simulator
    SH->>MAC: build + launch tagged macOS app
    MAC-->>SH: socket ready (wait_for_socket)
    SH->>MAC: workspace create (cmux_tagged)
    SH->>MAC: mint attach URL (mobile.attach_ticket.create)
    SH->>IOS: build + install (ios/scripts/reload.sh --no-launch)
    SH->>SIM: seed CMUX_DOGFOOD_ATTACH_URL into UserDefaults/launchctl
    SH->>SIM: launch iOS app (simctl launch)
    Note over SH,IOS: sleep 18 (fixed wait for attachment)
    SH->>SH: start_macos_recording + start_ios_recording
    SH->>MAC: send terminal keystrokes (cmux_tagged send)
    IOS-->>SIM: mirrors terminal content
    SH->>SH: stop_recorders
    SH->>SH: stitch_videos (ffmpeg side-by-side)
    SH->>WF: metadata.json + MP4 artifacts
Loading

Reviews (18): Last reviewed commit: "Open iOS workspace in sync recording" | Re-trigger Greptile

Comment on lines +266 to +267
VIDEO_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.mp4"
SCREENSHOT_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.png"

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 BUILD_TAG used raw in file paths without slug sanitization

The ios build step (line 141) sanitizes inputs.tag into a filesystem-safe slug before using it in paths (tr -c 'a-z0-9-' '-'). The ios-video step uses BUILD_TAG directly in VIDEO_PATH and SCREENSHOT_PATH. If the tag contains / (e.g. feature/my-branch), these paths will reference a non-existent subdirectory and xcrun simctl io recordVideo will fail, then the final [ -s "$VIDEO_PATH" ] guard will report "video not produced" without explaining the real cause. The metadata "video" key will also receive only the basename of the broken path.

Comment on lines +285 to +289
if [ -n "${TEST_FILTER:-}" ]; then
ONLY_TESTING=(-only-testing:"$TEST_FILTER")
else
ONLY_TESTING=(-only-testing:cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers)
fi

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 Unreachable else branch in ONLY_TESTING

TEST_FILTER is always defined in the step's env: block from inputs.test_filter, which has a non-empty default value. The else branch (and the duplicate fallback in metadata.json) are therefore dead code. Removing the redundant branch makes the intent clearer and avoids the confusion of two apparently authoritative default values.

Suggested change
if [ -n "${TEST_FILTER:-}" ]; then
ONLY_TESTING=(-only-testing:"$TEST_FILTER")
else
ONLY_TESTING=(-only-testing:cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers)
fi
ONLY_TESTING=(-only-testing:"$TEST_FILTER")

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +331 to +344
cat > "$GITHUB_WORKSPACE/artifact/metadata.json" <<JSON
{
"tag": "$BUILD_TAG",
"platform": "ios-video",
"runner": "${{ inputs.runner }}",
"sourceRef": "${{ inputs.ref }}",
"deviceFamily": "$DEVICE_FAMILY",
"simulatorId": "$SIMULATOR_ID",
"simulatorName": "$SIMULATOR_NAME",
"testFilter": "${TEST_FILTER:-cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers}",
"video": "$(basename "$VIDEO_PATH")",
"testExitCode": $test_rc
}
JSON

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 Shell variables interpolated into JSON without encoding

$SIMULATOR_NAME, $TEST_FILTER, and $BUILD_TAG are expanded directly into the JSON heredoc. If any value contains " or \ (e.g. a test filter path hand-edited by the dispatcher), the output file will be malformed and the downstream HQ parser may fail silently. The timings.json heredoc above has the same pattern with ${{ inputs.tag }} and ${{ inputs.ref }} (also unescaped), so the risk is already present there, but metadata.json adds more free-form shell variables. Generating the JSON with python3 -c "import json, os, sys; ..." or a jq -n call would guarantee well-formed output regardless of input characters.

@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 @.github/workflows/reload-build.yml:
- Around line 266-267: The VIDEO_PATH and SCREENSHOT_PATH variables are using
the raw BUILD_TAG value which can contain invalid path characters like forward
slashes, spaces, or shell special characters that will cause artifact creation
to fail. Sanitize the BUILD_TAG variable by applying the same
slugging/sanitization pattern that is already being used elsewhere in the iOS
archive step of the workflow, and use the sanitized version when constructing
both VIDEO_PATH and SCREENSHOT_PATH.
- Around line 331-343: The metadata.json generation in the heredoc uses direct
variable expansion which can break JSON validity and enable injection attacks if
inputs contain special characters or newlines. Replace the current heredoc
approach with jq -n to safely construct the JSON object, passing all dynamic
values (BUILD_TAG, DEVICE_FAMILY, SIMULATOR_ID, SIMULATOR_NAME, TEST_FILTER, the
basename of VIDEO_PATH, and test_rc) either as environment variables or using jq
--arg flags to ensure proper escaping and prevent injection vulnerabilities
while maintaining JSON integrity.
🪄 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: 49bca67f-dd18-443b-8f14-b2a0ef27227c

📥 Commits

Reviewing files that changed from the base of the PR and between 22ab7f1 and 4d97f30.

📒 Files selected for processing (1)
  • .github/workflows/reload-build.yml

Comment on lines +266 to +267
VIDEO_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.mp4"
SCREENSHOT_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.png"

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

Sanitize BUILD_TAG before using it in artifact filenames.

VIDEO_PATH/SCREENSHOT_PATH use raw BUILD_TAG; tags containing /, spaces, or shell-special chars can create invalid/nested paths and fail artifact creation. Reuse the slugging pattern already used in the iOS archive step.

Suggested patch
-          VIDEO_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.mp4"
-          SCREENSHOT_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${BUILD_TAG}.png"
+          SAFE_TAG="$(printf '%s' "$BUILD_TAG" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed 's/-\{2,\}/-/g; s/^-//; s/-$//')"
+          VIDEO_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${SAFE_TAG}.mp4"
+          SCREENSHOT_PATH="$GITHUB_WORKSPACE/artifact/cmux-ios-${SAFE_TAG}.png"
🤖 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/reload-build.yml around lines 266 - 267, The VIDEO_PATH
and SCREENSHOT_PATH variables are using the raw BUILD_TAG value which can
contain invalid path characters like forward slashes, spaces, or shell special
characters that will cause artifact creation to fail. Sanitize the BUILD_TAG
variable by applying the same slugging/sanitization pattern that is already
being used elsewhere in the iOS archive step of the workflow, and use the
sanitized version when constructing both VIDEO_PATH and SCREENSHOT_PATH.

Comment on lines +331 to +343
cat > "$GITHUB_WORKSPACE/artifact/metadata.json" <<JSON
{
"tag": "$BUILD_TAG",
"platform": "ios-video",
"runner": "${{ inputs.runner }}",
"sourceRef": "${{ inputs.ref }}",
"deviceFamily": "$DEVICE_FAMILY",
"simulatorId": "$SIMULATOR_ID",
"simulatorName": "$SIMULATOR_NAME",
"testFilter": "${TEST_FILTER:-cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers}",
"video": "$(basename "$VIDEO_PATH")",
"testExitCode": $test_rc
}

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

Harden metadata.json generation against template/script injection and malformed JSON.

Dynamic fields are injected directly into an unquoted heredoc (${{ ... }} and shell vars). This can break JSON and may allow template/script injection if dispatch inputs contain control characters/newlines. Build the JSON with jq -n (or Python) and pass all values via env/--arg instead of inline expansion.

Suggested patch
       - name: Record iOS simulator video
         if: ${{ inputs.platform == 'ios-video' }}
         env:
           BUILD_TAG: ${{ inputs.tag }}
           DEVICE_FAMILY: ${{ inputs.device_family }}
           TEST_FILTER: ${{ inputs.test_filter }}
+          INPUT_RUNNER: ${{ inputs.runner }}
+          INPUT_REF: ${{ inputs.ref }}
         run: |
@@
-          cat > "$GITHUB_WORKSPACE/artifact/metadata.json" <<JSON
-          {
-            "tag": "$BUILD_TAG",
-            "platform": "ios-video",
-            "runner": "${{ inputs.runner }}",
-            "sourceRef": "${{ inputs.ref }}",
-            "deviceFamily": "$DEVICE_FAMILY",
-            "simulatorId": "$SIMULATOR_ID",
-            "simulatorName": "$SIMULATOR_NAME",
-            "testFilter": "${TEST_FILTER:-cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers}",
-            "video": "$(basename "$VIDEO_PATH")",
-            "testExitCode": $test_rc
-          }
-          JSON
+          jq -n \
+            --arg tag "$BUILD_TAG" \
+            --arg platform "ios-video" \
+            --arg runner "$INPUT_RUNNER" \
+            --arg sourceRef "$INPUT_REF" \
+            --arg deviceFamily "$DEVICE_FAMILY" \
+            --arg simulatorId "$SIMULATOR_ID" \
+            --arg simulatorName "$SIMULATOR_NAME" \
+            --arg testFilter "${TEST_FILTER:-cmuxUITests/cmuxUITests/testStackAuthEntryUsesStableIdentifiers}" \
+            --arg video "$(basename "$VIDEO_PATH")" \
+            --argjson testExitCode "$test_rc" \
+            '{tag:$tag,platform:$platform,runner:$runner,sourceRef:$sourceRef,deviceFamily:$deviceFamily,simulatorId:$simulatorId,simulatorName:$simulatorName,testFilter:$testFilter,video:$video,testExitCode:$testExitCode}' \
+            > "$GITHUB_WORKSPACE/artifact/metadata.json"
🧰 Tools
🪛 zizmor (1.25.2)

[error] 335-335: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 336-336: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/reload-build.yml around lines 331 - 343, The metadata.json
generation in the heredoc uses direct variable expansion which can break JSON
validity and enable injection attacks if inputs contain special characters or
newlines. Replace the current heredoc approach with jq -n to safely construct
the JSON object, passing all dynamic values (BUILD_TAG, DEVICE_FAMILY,
SIMULATOR_ID, SIMULATOR_NAME, TEST_FILTER, the basename of VIDEO_PATH, and
test_rc) either as environment variables or using jq --arg flags to ensure
proper escaping and prevent injection vulnerabilities while maintaining JSON
integrity.

Source: Linters/SAST tools

xcrun simctl delete "$SIMULATOR_ID" >/dev/null 2>&1 || true
fi
}
trap cleanup_video_run EXIT

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

EXIT trap registered too late

Medium Severity

The ios-video step boots and erases a simulator, then runs build-for-testing, but the EXIT trap that shuts down (and deletes created) simulators is not installed until after that build. Any failure from boot through build-for-testing exits with set -e without running cleanup, leaving a simulator running on the runner.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 23ee095. Configure here.

Comment thread .github/workflows/reload-build.yml
Comment thread .github/workflows/reload-build.yml

@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: 3

🤖 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 `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 89-99: Replace the three fixed Thread.sleep calls in the test
method with predicate-based waits to make the test deterministic. For the first
sleep before input.tap(), wait for the input element to be hittable using
waitForExistence or hitPoint checks. For the second sleep after
typeText(command) and before typeText("\r"), remove the sleep since the previous
wait and tap operations should complete the input preparation. For the third
sleep after the terminal assertions at the end of the method, replace it with a
wait on an XCTNSPredicate that verifies the terminal's label contains the
expected marker rather than using a fixed timeout. Use XCUIApplication's
predicate waiting mechanisms with appropriate timeouts to ensure the UI is in
the expected state before proceeding.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift`:
- Around line 65-72: The shouldShowSyncTypingDemo property and the sync-typing
demo routing logic should be moved out of the production Sources directory since
they are debug/test-only seams that do not gate real production behavior.
Extract the shouldShowSyncTypingDemo property and the conditional branching at
the root-content path in CMUXMobileRootView from the production source code and
relocate this debug-only logic to an appropriate test or debug-only
module/configuration. This ensures the production code path remains clean and
debug facilities are properly isolated as per project guidelines.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SyncTypingDemoView.swift`:
- Around line 11-14: The user-facing strings in the output array ("cmux iOS
terminal", "host: cloud macOS runner", and the string containing "$ \(draft)")
are not localized and must be wrapped with String(localized:...,
defaultValue:...) to comply with localization guidelines. For each string in the
array, wrap it with String(localized:) using an appropriate localization key and
set the defaultValue parameter to the current English string. Ensure the string
interpolation for draft is preserved within the localized string.
🪄 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: ba80b26b-0df8-4b04-b09c-55efa7f83773

📥 Commits

Reviewing files that changed from the base of the PR and between 23ee095 and 90b3509.

📒 Files selected for processing (5)
  • .github/workflows/reload-build.yml
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SyncTypingDemoView.swift
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swift
  • ios/cmuxUITests/cmuxUITests.swift

Comment thread ios/cmuxUITests/cmuxUITests.swift Outdated
Comment on lines +89 to +99
Thread.sleep(forTimeInterval: 1.0)
input.tap()
input.typeText(command)
Thread.sleep(forTimeInterval: 1.0)
input.typeText("\r")

let terminal = app.otherElements["MobileSyncDemoTerminal"]
XCTAssertTrue(terminal.waitForExistence(timeout: 4))
XCTAssertTrue(terminal.label.contains(marker), "Expected demo terminal to contain \(marker). Label: \(terminal.label)")
Thread.sleep(forTimeInterval: 2.0)
}

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

Replace fixed sleeps with readiness-driven waits.

Line 89, Line 92, and Line 98 use fixed Thread.sleep, which makes this UI test timing-dependent and flaky under CI load. Wait on real predicates (focus/label state) instead.

As per coding guidelines, tests must not use fixed sleep to wait for async readiness; they should wait on completion signals or deadline-bounded predicate polls.

Proposed fix
-        Thread.sleep(forTimeInterval: 1.0)
         input.tap()
         input.typeText(command)
-        Thread.sleep(forTimeInterval: 1.0)
         input.typeText("\r")
 
         let terminal = app.otherElements["MobileSyncDemoTerminal"]
         XCTAssertTrue(terminal.waitForExistence(timeout: 4))
-        XCTAssertTrue(terminal.label.contains(marker), "Expected demo terminal to contain \(marker). Label: \(terminal.label)")
-        Thread.sleep(forTimeInterval: 2.0)
+        let expectation = XCTNSPredicateExpectation(
+            predicate: NSPredicate { object, _ in
+                guard let terminal = object as? XCUIElement else { return false }
+                return terminal.label.contains(marker)
+            },
+            object: terminal
+        )
+        XCTAssertEqual(XCTWaiter.wait(for: [expectation], timeout: 6), .completed)
🤖 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 `@ios/cmuxUITests/cmuxUITests.swift` around lines 89 - 99, Replace the three
fixed Thread.sleep calls in the test method with predicate-based waits to make
the test deterministic. For the first sleep before input.tap(), wait for the
input element to be hittable using waitForExistence or hitPoint checks. For the
second sleep after typeText(command) and before typeText("\r"), remove the sleep
since the previous wait and tap operations should complete the input
preparation. For the third sleep after the terminal assertions at the end of the
method, replace it with a wait on an XCTNSPredicate that verifies the terminal's
label contains the expected marker rather than using a fixed timeout. Use
XCUIApplication's predicate waiting mechanisms with appropriate timeouts to
ensure the UI is in the expected state before proceeding.

Source: Coding guidelines

Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift Outdated
Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SyncTypingDemoView.swift Outdated
Comment thread scripts/ci/record-real-sync-video.sh
Comment thread scripts/ci/record-real-sync-video.sh Outdated
xcrun simctl terminate "$SIMULATOR_ID" "$IOS_BUNDLE_ID" >/dev/null 2>&1 || true
xcrun simctl launch "$SIMULATOR_ID" "$IOS_BUNDLE_ID" >/dev/null
sleep 2
xcrun simctl openurl "$SIMULATOR_ID" "$ATTACH_URL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

iOS install uses different simulator

Medium Severity

Sync-video selects a simulator by UDID, boots it, and records from that UDID, but ios/scripts/reload.sh is invoked with --simulator name only and resolves the first available device with that name. When multiple simulators share the same display name, the built app may be installed on a different UDID than the one launched, opened, and recorded.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 435b4cc. Configure here.

@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: 5

🤖 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/record-real-sync-video.sh`:
- Around line 291-294: The sleep 2 (after the xcrun simctl launch command for
"$SIMULATOR_ID" and "$IOS_BUNDLE_ID") and sleep 5 (after the xcrun simctl
openurl command) are fixed wall-clock delays used to wait for app-launch and
URL-handling readiness, which are unreliable and cause flakiness. Replace these
fixed delays with proper readiness signals by polling the app's log state,
checking the simulator process state, or verifying the attach handshake has
completed, rather than waiting for elapsed time to pass.
- Around line 196-198: The `sleep 2` followed by a single `kill -0` check is an
unreliable wall-clock synchronization hack that does not actually verify the
macOS recorder is ready; replace this with active polling of the MAC_RECORD_LOG
file to check for ffmpeg's actual readiness signal (such as "Recording started"
output), similar to how the `start_ios_recording` function handles process
readiness. Remove the `sleep 2` and `kill -0` commands and instead implement a
polling loop that reads MAC_RECORD_LOG and waits until the expected ffmpeg
startup message appears or a timeout is reached.
- Around line 32-34: Replace BUILD_TAG with TAG_SLUG in the variable assignments
for MAC_RAW_VIDEO, IOS_RAW_VIDEO, and FINAL_VIDEO. The raw BUILD_TAG can contain
special characters like slashes, spaces, or other characters that create invalid
file paths and cause artifact upload failures, while TAG_SLUG (already computed
earlier in the script) is the sanitized version designed for safe use in
filenames.
- Around line 247-248: The `eval "$(select_simulator)"` pattern swallows
failures because command substitution doesn't trigger errexit. Instead, capture
the output of select_simulator into a temporary variable or file, check its exit
status explicitly, and only then eval or source that output. This ensures that
if select_simulator fails (e.g., when no iOS simulator runtime is available),
the script will exit with an error rather than proceeding with empty
SIMULATOR_ID, SIMULATOR_NAME, and SIMULATOR_CREATED variables that cause
confusing downstream failures in xcrun simctl erase and recording steps.
- Around line 318-338: The Python here-document passes shell variables
(BUILD_TAG, SIMULATOR_NAME, WORKSPACE_ID, SYNC_MARKER, etc.) through direct
interpolation into the JSON payload, creating a security risk where special
characters or command substitutions in these variables could corrupt the JSON or
execute arbitrary code. Instead of shell-expanding variables directly into the
Python source, pass them as command-line arguments to the Python script using
sys.argv or via os.environ, then reference them within the Python code using
these safe mechanisms rather than relying on shell interpolation.
🪄 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: 2f35e361-1563-4b70-a26e-618eaeec5abd

📥 Commits

Reviewing files that changed from the base of the PR and between 90b3509 and 435b4cc.

📒 Files selected for processing (2)
  • .github/workflows/reload-build.yml
  • scripts/ci/record-real-sync-video.sh

Comment thread scripts/ci/record-real-sync-video.sh
Comment thread scripts/ci/record-real-sync-video.sh Outdated
Comment on lines +247 to +248
eval "$(select_simulator)"
export SIMULATOR_ID SIMULATOR_NAME SIMULATOR_CREATED

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

eval "$(select_simulator)" swallows failures under set -e.

A command substitution failure in this position does not trigger errexit. If select_simulator raises SystemExit (e.g. "No available iOS simulator runtime"), stdout is empty, eval is a no-op, and the script proceeds with an empty SIMULATOR_ID into xcrun simctl erase "" and recording — producing confusing downstream failures. Capture and check explicitly (the ios-video yml step avoids this by redirecting to a file and .-sourcing it).

🛠️ Proposed fix
-eval "$(select_simulator)"
+sim_env="$(select_simulator)" || { echo "simulator selection failed" >&2; exit 1; }
+eval "$sim_env"
 export SIMULATOR_ID SIMULATOR_NAME SIMULATOR_CREATED
+[[ -n "$SIMULATOR_ID" ]] || { echo "no simulator id resolved" >&2; exit 1; }
📝 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
eval "$(select_simulator)"
export SIMULATOR_ID SIMULATOR_NAME SIMULATOR_CREATED
sim_env="$(select_simulator)" || { echo "simulator selection failed" >&2; exit 1; }
eval "$sim_env"
export SIMULATOR_ID SIMULATOR_NAME SIMULATOR_CREATED
[[ -n "$SIMULATOR_ID" ]] || { echo "no simulator id resolved" >&2; exit 1; }
🤖 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/record-real-sync-video.sh` around lines 247 - 248, The `eval
"$(select_simulator)"` pattern swallows failures because command substitution
doesn't trigger errexit. Instead, capture the output of select_simulator into a
temporary variable or file, check its exit status explicitly, and only then eval
or source that output. This ensures that if select_simulator fails (e.g., when
no iOS simulator runtime is available), the script will exit with an error
rather than proceeding with empty SIMULATOR_ID, SIMULATOR_NAME, and
SIMULATOR_CREATED variables that cause confusing downstream failures in xcrun
simctl erase and recording steps.

Comment thread scripts/ci/record-real-sync-video.sh Outdated
Comment thread scripts/ci/record-real-sync-video.sh
xcrun simctl shutdown "$SIMULATOR_ID" >/dev/null 2>&1 || true
xcrun simctl erase "$SIMULATOR_ID"
xcrun simctl boot "$SIMULATOR_ID" >/dev/null 2>&1 || true
xcrun simctl bootstatus "$SIMULATOR_ID" -b

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Simulator boot lacks timeout

Medium Severity

The ios-video step runs xcrun simctl bootstatus … -b with no timeout, while sync-video wraps the same wait in timeout 120s. A stuck simulator boot can hold the job until the 60-minute workflow limit instead of failing fast with logs.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 67e7023. Configure here.

const { attachURL } = buildAttachURL(JSON.parse(process.env.PAYLOAD), { routeKind: "debug_loopback" });
process.stdout.write(attachURL);
NODE
return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Attach URL mint skips retries

Medium Severity

mint_attach_url treats any non-empty RPC payload as success and returns immediately after node, without checking that a URL was written or retrying when buildAttachURL fails (e.g. debug_loopback route not ready). Transient failures abort the whole sync run instead of polling like dev-setup.sh.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 67e7023. Configure here.

Comment on lines +53 to +55
MAC_RAW_VIDEO="$ARTIFACT_DIR/cmux-macos-${BUILD_TAG}.mp4"
IOS_RAW_VIDEO="$ARTIFACT_DIR/cmux-ios-${BUILD_TAG}.mp4"
FINAL_VIDEO="$ARTIFACT_DIR/cmux-real-sync-left-right-${BUILD_TAG}.mp4"

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 $BUILD_TAG used raw in video output paths while TAG_SLUG is already computed just above for filesystem-safe slugs. A tag containing / (e.g. feature/my-branch) produces a path like $ARTIFACT_DIR/cmux-macos-feature/my-branch.mp4 whose parent directory never gets created, causing ffmpeg and simctl to fail with a misleading "no such file or directory" error.

Suggested change
MAC_RAW_VIDEO="$ARTIFACT_DIR/cmux-macos-${BUILD_TAG}.mp4"
IOS_RAW_VIDEO="$ARTIFACT_DIR/cmux-ios-${BUILD_TAG}.mp4"
FINAL_VIDEO="$ARTIFACT_DIR/cmux-real-sync-left-right-${BUILD_TAG}.mp4"
MAC_RAW_VIDEO="$ARTIFACT_DIR/cmux-macos-${TAG_SLUG}.mp4"
IOS_RAW_VIDEO="$ARTIFACT_DIR/cmux-ios-${TAG_SLUG}.mp4"
FINAL_VIDEO="$ARTIFACT_DIR/cmux-real-sync-left-right-${TAG_SLUG}.mp4"

@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.

There are 8 total unresolved issues (including 7 from previous reviews).

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 af7537a. Configure here.

set -e
if ! grep -Eq 'Executed [1-9][0-9]* tests?, with 0 failures' "$TEST_LOG"; then
echo "ios-video did not execute any selected UI tests" >&2
test_rc=65

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 UI tests misreported

Medium Severity

The post-test grep treats any log line that is not Executed … with 0 failures as “no UI tests ran,” so a failing test overwrites xcodebuild’s exit code with 65 and prints the wrong stderr message. metadata.json’s testExitCode can misreport real UI failures.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit af7537a. Configure here.

Comment thread scripts/ci/record-real-sync-video.sh Outdated
Comment on lines +373 to +375
run_with_timeout 90 scripts/mobile-dev-launch.sh --tag "$BUILD_TAG" --simulator "$SIMULATOR_NAME" --attach --detach
unset CMUX_DOGFOOD_ATTACH_URL
sleep 10

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 Fixed sleep used to synchronize iOS app attachment lifecycle

mobile-dev-launch.sh is called with --detach, meaning it exits before the iOS app has established its terminal connection. The hard-coded sleep 10 that follows is the only mechanism ensuring the app is attached before start_ios_recording and the terminal-send sequence begin. On a slow macOS runner — or any runner where the iOS simulator takes longer than 10 s to mount the app and complete attachment — recording begins with an empty/disconnected iOS screen, producing a demo video that silently shows the wrong state. There is no fallback: if the app is not attached within 10 s, the script continues regardless.

A bounded readiness loop polling a real signal (e.g. cmux_tagged read-screen succeeding on the iOS surface, or querying the iOS app's terminal session status via the RPC) would guarantee attachment before recording starts, matching the pattern already used for the macOS workspace at lines 358-363.

Comment on lines +410 to +430
python3 - "$METADATA_PATH" <<PY
import json
import pathlib
import sys

path = pathlib.Path(sys.argv[1])
path.write_text(json.dumps({
"tag": "$BUILD_TAG",
"platform": "sync-video",
"mode": "real-cmux-desktop-ios",
"deviceFamily": "$DEVICE_FAMILY",
"simulatorId": "$SIMULATOR_ID",
"simulatorName": "$SIMULATOR_NAME",
"workspaceId": "$WORKSPACE_ID",
"surfaceId": "$SURFACE_ID",
"syncMarker": "$SYNC_MARKER",
"video": "$(basename "$FINAL_VIDEO")",
"macVideo": "$(basename "$MAC_RAW_VIDEO")",
"iosVideo": "$(basename "$IOS_RAW_VIDEO")",
}, indent=2) + "\n")
PY

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 Python source injection via unquoted heredoc

The metadata heredoc uses <<PY (unquoted), so the shell expands $BUILD_TAG, $SIMULATOR_NAME, $WORKSPACE_ID, $SURFACE_ID, and friends directly into Python string literals before Python ever runs. If any of those values contains a backslash, double-quote, or newline — $BUILD_TAG is user-controlled via inputs.tag — the generated Python source is syntactically invalid, Python exits non-zero, metadata.json is never written, and the workflow step fails with a confusing SyntaxError.

Compare mint_attach_url at line 192, which correctly uses <<'PY' (quoted, suppresses expansion) and passes values through sys.argv[]. The same pattern should be used here: quote the delimiter and thread each shell variable in via a positional argument or an env-var key that Python reads with os.environ.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 0af62f1f Deployed Jun 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants