Bug description
Ending a Call Link by tapping the red end-call button reproducibly crashes the app in Debug builds. The crash is an owsFailDebug (fatalError in Debug, no-op in Release) inside CallAudioService.setAudioSession(category:mode:options:), triggered when AVAudioSession.setCategory throws during call teardown.
This affects Debug builds only. Release builds (TestFlight / App Store) catch the same throw and log it without crashing, so end users on production builds do not see this. It does, however, reproducibly crash any contributor running a Debug build the moment they end a Call Link, which makes development of any call-related code on a real device awkward.
Reproduces every time, on both an iPhone 14 Pro (iOS 26.1) and an iPad Pro 12.9" 5th-gen / M1 (iPadOS 26.4).
Steps to reproduce
- Build and run the
Signal scheme in Debug on a real iOS device
- Open or create a Call Link and join the call
- Tap the red end-call button
Actual result: App crashes with EXC_BREAKPOINT / SIGTRAP from owsFailDebug inside CallAudioService.setAudioSession.
Expected result: The Call Link ends and the user returns to the chat list without crash. (The audio-session change failure during teardown is benign — the call is ending anyway — and should be logged at warn/error level, not fatal-asserted.)
Stack trace (top frames, faulting thread)
0 Signal _assertionFailure(_:_:file:line:flags:)
1 Signal fatalError(_:file:line:)
2 Signal assertionFailure(_:file:line:)
3 Signal owsFailDebug(_:logger:file:function:line:)
4 Signal CallAudioService.setAudioSession(category:mode:options:)
5 Signal CallAudioService.ensureProperAudioSession(call:)
6 Signal CallAudioService.groupCallLocalDeviceStateChanged(_:)
7 Signal protocol witness for GroupCallObserver.groupCallLocalDeviceStateChanged(_:)
8 Signal closure #1 in GroupCall.groupCall(onLocalDeviceStateChanged:)
9 Signal Sequence.forEach(_:)
10 Signal GroupCall.groupCall(onLocalDeviceStateChanged:)
11 Signal protocol witness for GroupCallDelegate.groupCall(onLocalDeviceStateChanged:)
12 Signal GroupCall.handleJoinStateChanged(joinState:demuxId:)
13 Signal closure #1 in CallManager.handleJoinStateChanged(clientId:joinState:demuxId:)
Root cause
Signal/Calls/CallAudioService.swift (around the setAudioSession private method) catches a throw from AVAudioSession.setCategory and reports it via owsFailDebug:
do {
try avAudioSession.setCategory(category, mode: mode, options: options)
Logger.info("AVAudioSession changed from [...] to [...]")
} catch {
let message = "AVAudioSession failed to change from [...] to [...] with error: \(error)"
owsFailDebug(message)
}
When the user ends a Call Link, RingRTC fires handleJoinStateChanged (the user is leaving the call), which propagates to CallAudioService.groupCallLocalDeviceStateChanged → ensureProperAudioSession → setAudioSession. At that moment iOS is concurrently deactivating the audio session as part of the call ending, so setCategory throws, and owsFailDebug turns the throw into a fatal assertion in Debug.
Suggested handling
The catch site is the right place to address this. A couple of options:
- Downgrade the catch from
owsFailDebug to Logger.warn (or Logger.error) — the call is ending anyway, so the audio session not updating to the new category is not a meaningful failure mode worth a fatal assertion.
- Detect the call-teardown timing window and skip the
setCategory attempt, or special-case the specific NSError domain/code that iOS returns when the session is being deactivated.
(1) is the smaller change.
Screenshots
n/a — backtrace-only crash.
Device info
Reproducible on both devices in Debug builds:
Device: iPhone 14 Pro
iOS version: 26.1
Signal version: 8.9 (locally built, Debug)
Device: iPad Pro 12.9" (5th generation, M1)
iOS version: iPadOS 26.4
Signal version: 8.9 (locally built, Debug)
Link to debug log
n/a — this is a developer-only crash that fires before the in-app "Submit Debug Log" flow can be exercised. Stack trace from the .ips crash report is included above; the full crash report can be supplied via email per the bug-report template instructions if it would help.
Bug description
Ending a Call Link by tapping the red end-call button reproducibly crashes the app in Debug builds. The crash is an
owsFailDebug(fatalErrorin Debug, no-op in Release) insideCallAudioService.setAudioSession(category:mode:options:), triggered whenAVAudioSession.setCategorythrows during call teardown.This affects Debug builds only. Release builds (TestFlight / App Store) catch the same throw and log it without crashing, so end users on production builds do not see this. It does, however, reproducibly crash any contributor running a Debug build the moment they end a Call Link, which makes development of any call-related code on a real device awkward.
Reproduces every time, on both an iPhone 14 Pro (iOS 26.1) and an iPad Pro 12.9" 5th-gen / M1 (iPadOS 26.4).
Steps to reproduce
Signalscheme in Debug on a real iOS deviceActual result: App crashes with
EXC_BREAKPOINT/SIGTRAPfromowsFailDebuginsideCallAudioService.setAudioSession.Expected result: The Call Link ends and the user returns to the chat list without crash. (The audio-session change failure during teardown is benign — the call is ending anyway — and should be logged at warn/error level, not fatal-asserted.)
Stack trace (top frames, faulting thread)
Root cause
Signal/Calls/CallAudioService.swift(around thesetAudioSessionprivate method) catches a throw fromAVAudioSession.setCategoryand reports it viaowsFailDebug:When the user ends a Call Link, RingRTC fires
handleJoinStateChanged(the user is leaving the call), which propagates toCallAudioService.groupCallLocalDeviceStateChanged → ensureProperAudioSession → setAudioSession. At that moment iOS is concurrently deactivating the audio session as part of the call ending, sosetCategorythrows, andowsFailDebugturns the throw into a fatal assertion in Debug.Suggested handling
The catch site is the right place to address this. A couple of options:
owsFailDebugtoLogger.warn(orLogger.error) — the call is ending anyway, so the audio session not updating to the new category is not a meaningful failure mode worth a fatal assertion.setCategoryattempt, or special-case the specificNSErrordomain/code that iOS returns when the session is being deactivated.(1) is the smaller change.
Screenshots
n/a — backtrace-only crash.
Device info
Reproducible on both devices in Debug builds:
Device: iPhone 14 Pro
iOS version: 26.1
Signal version: 8.9 (locally built, Debug)
Device: iPad Pro 12.9" (5th generation, M1)
iOS version: iPadOS 26.4
Signal version: 8.9 (locally built, Debug)
Link to debug log
n/a — this is a developer-only crash that fires before the in-app "Submit Debug Log" flow can be exercised. Stack trace from the
.ipscrash report is included above; the full crash report can be supplied via email per the bug-report template instructions if it would help.