fix: prevent SIGSEGV crash when Stop Recording clicked after mic replug - #121
Merged
Conversation
… SIGSEGV crash
Bare Task { } closures inside SwiftUI Button actions caused an
EXC_BAD_ACCESS (SIGSEGV) crash on macOS 26 when the Swift concurrency
runtime called MainActor.assumeIsolated() on a stale executor reference.
This was reliably triggered by:
1. Replug a microphone while recording (causes AVAudioEngineConfigurationChange)
2. App auto-recovers via onAudioDeviceChanged → sets appStatus = .idle
3. User clicks Stop Recording button on the now-stale SwiftUI view node
4. _ButtonGesture dispatches via MainActor.assumeIsolated() → reads freed memory → crash
Fix: annotate every Task spawned from a Button/onAppear in views with
@mainactor, which removes the implicit assumeIsolated() call and binds
the task directly to the main actor instead.
Also reset silenceCallbackFired and maxDurationCallbackFired in
AudioEngine.handleAudioConfigurationChange so stale callback state
cannot fire spurious stop/timeout events after a device reconnect.
jatinkrmalik
added a commit
that referenced
this pull request
Apr 24, 2026
Patch release covering all changes since v0.6.0: - fix(#121): SIGSEGV crash when stopping recording after mic replug - fix(#121): clear stale AudioEngine callback flags on device change - docs(#120): explain GitHub 403 update-check error on shared networks - ci(#122): bump GitHub Actions to Node.js 24-compatible versions No new features and no breaking changes — PATCH bump per SemVer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
@slatlasdev is hitting a daily crash:
appStatus → .idle)EXC_BAD_ACCESS (SIGSEGV),KERN_INVALID_ADDRESSRoot Cause
The crash trace from the IPS report (incident
48DBB805-253C-4F8B-9DCE-D6D389E10CE7, Sam Leatherdale, 2026-04-23):SwiftUI's
_ButtonGestureinternally wraps button action closures withMainActor.assumeIsolated { }. When a bareTask { }is used inside a Button action, the Swift 6 runtime synthesises an implicitassumeIsolatedhop to validate the current executor. On macOS 26, if the view is being torn down or re-evaluated at the same moment the gesture fires — exactly what happens whenonAudioDeviceChangedmutates@Publishedstate and removes the Stop button while the user's click is mid-dispatch — the executor reference the runtime reads has already been freed, causing the SIGSEGV.Why mic replug reliably triggers it:
AVAudioEngineConfigurationChangefires →handleAudioConfigurationChangetears down engine, dispatchesonAudioDeviceChangedon mainonAudioDeviceChangedimmediately setsisRecording = false+appStatus = .idlestatusSection, removing the Stop Recording button from the view tree_ButtonGesturenode → crashFixes
1.
Task { @MainActor in }in all view button actions (primary fix)Adding
@MainActorto theTaskclosure binds it directly to the main actor's serial executor at the call site, eliminating the implicitassumeIsolatedwrapper that SwiftUI synthesises. This is the correct Swift 6 pattern for all Button actions that call@MainActor-isolated methods.Files changed:
MenuBarView.swift— Stop Recording buttonUpdateView.swift— Download & Install, Retry buttonsOnboardingView.swift—onAppearstartup, model select/download, test recording toggleSettingsView.swift— Load, Download & Load, force-download alert, Check for Updates buttons2. Reset stale callback flags in
AudioEngine.handleAudioConfigurationChange(secondary fix)When the audio device changes mid-recording, the handler was already stopping the engine and resetting
isCurrentlyRecording, but leftsilenceCallbackFiredandmaxDurationCallbackFiredset. On the next recording session after replug, those staletrueflags meant neither the silence detector nor the max-duration limiter would ever fire — so recordings could run indefinitely with no auto-stop. Both flags are now cleared alongsideisCurrentlyRecording.Testing
swift test)References
EXC_BAD_ACCESS / SIGSEGV, incident48DBB805-253C-4F8B-9DCE-D6D389E10CE7