Skip to content

Fix crashes when recording on an external display (e.g. 5K Studio Display)#2

Open
junyetong wants to merge 4 commits into
nolangz:mainfrom
junyetong:fix/studio-display-executor-crash
Open

Fix crashes when recording on an external display (e.g. 5K Studio Display)#2
junyetong wants to merge 4 commits into
nolangz:mainfrom
junyetong:fix/studio-display-executor-crash

Conversation

@junyetong

@junyetong junyetong commented Jun 25, 2026

Copy link
Copy Markdown

Summary

On macOS 26 with Apple Silicon, recording would crash in several ways once a Studio Display was attached. This PR makes full-screen recording on a 5K Studio Display work end-to-end. It addresses three distinct crashes found while chasing the original report.

1. MainActor executor corruption (the original crash)

Connecting a Studio Display (or any display/audio route change) could leave the Swift 6 runtime with a corrupted MainActor SerialExecutorRef. The next SwiftUI button dispatch then hit swift_task_isCurrentExecutorImpl / MainActor.assumeIsolated and crashed — first as EXC_BAD_ACCESS, and as EXC_BREAKPOINT (SIGTRAP) once the legacy executor flag was set.

Root cause: startRecording / stopRecording / preview setup suspended the main actor across awaits (SCShareableContent enumeration, stream.startCapture, camera startup, etc.).

Fix: move the whole start/stop/preview orchestration into nonisolated contexts (Task.detached) and only touch MainActor state through synchronous MainActor.run blocks, so the main actor never suspends across those awaits. CameraManager, AVAudioEngineRecorder, RecordingMetricsRecorder and the camera frame helpers are made nonisolated / Sendable accordingly, with an NSLock guarding capture-session state. The existing LSEnvironment / setenv legacy-executor flag is kept as belt-and-suspenders.

2. H.264 limit at 5K

A 5K Studio Display full-screen capture is 5120x2880, which is above the H.264 hardware encoder limit and aborted AVAssetWriterInput initialization. Now HEVC is selected automatically when either dimension exceeds 4096px; H.264 is still used otherwise.

3. ASR model load aborts the app when Git LFS isn't pulled

The Paraformer .onnx weights are stored via Git LFS. On a checkout without git lfs pull they are ~130-byte pointer files, so hasRequiredFiles() passed and ONNX Runtime threw an uncaught C++ exception while building the session (Ort::Session::Session -> std::terminate -> abort) the moment a recorded teleprompter session started. hasRequiredFiles() now also checks a realistic minimum file size, so a missing/pointer-only model surfaces as a recoverable ASRError (with the existing setup-script guidance) instead of crashing.

Environment

  • Mac17,8 (M5 Pro), macOS 26.5.1, 5K Studio Display as main display.

Test plan

  • Release build succeeds (xcodebuild -scheme CueRecord -configuration Release).
  • Full-screen recording starts and runs on a 5K Studio Display without crashing.
  • Area / window recording on the built-in display still works.
  • Camera overlay + microphone + system audio still captured.
  • Recorded teleprompter (speech recognition) works with the model present, and shows a friendly error (no crash) when the LFS model is missing.

junyetong and others added 4 commits June 25, 2026 09:10
Connecting a Studio Display (or other display/audio route change) could
crash the app with EXC_BAD_ACCESS while SwiftUI dispatches a Button
gesture:

    libobjc            lookUpImpOrForward
    libswiftCore       swift_getObjectType
    Concurrency        swift_task_isCurrentExecutorWithFlagsImpl
    SwiftUI            MainActor.assumeIsolated
    SwiftUI            _ButtonGesture.internalBody

This is the known Swift 6 / macOS 26 concurrency runtime issue where the
"is current executor" check (enabled for apps built against the Fall 2024+
SDK) is handed a corrupted SerialExecutorRef and crashes instead of
returning, rather than an app logic bug. A display/audio route change
(such as plugging in a Studio Display) destabilizes the executor state and
makes the next button tap fault.

Restore the pre-Swift-6, non-crashing executor check via LSEnvironment so
the runtime no longer aborts on this path.

See: swiftlang/swift runtime flag
SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE (Bincompat.cpp).
Add a setenv in CueRecordApp.init() (overwrite=0) so the
SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE flag is also applied for
launch paths that bypass LaunchServices and would not pick up the
Info.plist LSEnvironment value. Belt-and-suspenders for the same crash.
Connecting a Studio Display (or any display/audio route change) could leave
the Swift 6 runtime with a corrupted MainActor SerialExecutorRef. The next
SwiftUI button dispatch then hit swift_task_isCurrentExecutorImpl /
MainActor.assumeIsolated and crashed (EXC_BAD_ACCESS, later EXC_BREAKPOINT)
because start/stop recording suspended the main actor across awaits
(SCShareableContent enumeration, stream.startCapture, camera startup, etc).

Move the whole start/stop/preview orchestration into nonisolated contexts
(Task.detached) and only touch MainActor state through synchronous
MainActor.run blocks, so the main actor never suspends across those awaits.
CameraManager, AVAudioEngineRecorder, RecordingMetricsRecorder and the
camera frame helpers are made nonisolated / Sendable accordingly, with an
NSLock guarding capture-session state.

Also pick HEVC instead of H.264 when either dimension exceeds 4096px: a 5K
Studio Display full-screen capture (5120x2880) is above the H.264 hardware
encoder limit and aborted AVAssetWriterInput initialization.

Co-authored-by: Cursor <cursoragent@cursor.com>
The Paraformer .onnx weights are stored via Git LFS. On a checkout without
`git lfs pull` they exist as ~130-byte pointer files, so hasRequiredFiles()
passed and ONNX Runtime then threw an uncaught C++ exception while building
the session (Ort::Session::Session -> std::terminate -> abort) the moment a
recorded teleprompter session started.

Require a realistic minimum size for tokens.txt and the encoder/decoder so a
missing or pointer-only model surfaces as a recoverable ASRError (handled by
start()) with the existing "run scripts/setup_sherpa_onnx.sh" guidance,
instead of crashing the whole app.

Co-authored-by: Cursor <cursoragent@cursor.com>
@junyetong junyetong changed the title Fix crash when connecting an external display (Studio Display) Fix crashes when recording on an external display (e.g. 5K Studio Display) Jun 25, 2026
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