← Back to PRs

#18235: macOS: prevent Voice Wake crash when no input device is available

by agisilaos open 2026-02-16 16:46 View on GitHub →
app: macos stale size: S
## Summary - prevent macOS app crash when Voice Wake starts without a usable default audio input device - add CoreAudio preflight for default input availability before installing AVAudioEngine tap - keep failure in the recoverable runtime path (log + stop), avoiding SIGABRT process termination ## Bug On Macs with no usable input device (common on Mac mini/headless setups), enabling Voice Wake can crash: - `AVAudioNode.installTapOnBus` throws Objective-C exception - exception crosses into Swift runtime and aborts the app (`SIGABRT`) ## Root Cause `VoiceWakeRuntime.start(with:)` proceeded to `installTap` without verifying that the default input device is both present and input-capable/alive. ## Changes 1. `AudioInputDeviceObserver` - added `hasUsableDefaultInputDevice()` - added `inputAvailabilitySummary()` for diagnostics - added private helper for default UID vs alive input UID matching 2. `VoiceWakeRuntime.start(with:)` - added startup guard using `AudioInputDeviceObserver.hasUsableDefaultInputDevice()` before installing tap - throws normal `NSError` (with input summary) and exits via existing catch/stop flow 3. Tests - added `AudioInputDeviceObserverTests` covering: - missing default UID - default UID not alive/input-capable - default UID valid/alive ## Validation - `swift test --filter AudioInputDeviceObserverTests` - `swift test --filter VoiceWakeRuntimeTests` ## Fixes - Fixes #17484 - Fixes #16418 - Fixes #16118 - Fixes #15740 - Fixes #12169 <!-- greptile_comment --> <h3>Greptile Summary</h3> Prevents a crash (`SIGABRT`) on Macs without a usable audio input device (e.g., Mac mini/headless setups) by adding a CoreAudio preflight check before installing the `AVAudioEngine` tap in `VoiceWakeRuntime.start(with:)`. The new `AudioInputDeviceObserver.hasUsableDefaultInputDevice()` method verifies the default input device UID is both present and alive before proceeding, converting what was a fatal ObjC exception into a recoverable error handled through the existing catch/stop flow. - `AudioInputDeviceObserver` gains `hasUsableDefaultInputDevice()` and `inputAvailabilitySummary()` with a clean private helper that matches the default device UID against alive input UIDs - `VoiceWakeRuntime.start(with:)` now guards on input device availability before engine creation, throwing a descriptive `NSError` that the existing `catch` block handles gracefully - New `AudioInputDeviceObserverTests` cover nil UID, mismatched UID, and valid UID cases via a `#if DEBUG` test hook - Note: `VoiceWakeTester` and `MicLevelMonitor` have similar `installTap` patterns without this preflight guard — they may benefit from the same protection in a follow-up, though they are less critical since they are user-initiated rather than automatic <h3>Confidence Score: 5/5</h3> - This PR is safe to merge — it adds a defensive guard that prevents a crash on the error path without changing any happy-path behavior. - The change is small, well-scoped, and only adds a guard before existing code. The new check is purely additive — when a usable input device exists, behavior is identical to before. When no device exists, the code now takes a graceful error path instead of crashing. The logic is simple (UID lookup + set membership) and tested. No existing tests or behavior are affected. - No files require special attention. Consider applying the same `hasUsableDefaultInputDevice()` guard to `VoiceWakeTester.swift` and `MicLevelMonitor.swift` in a follow-up. <sub>Last reviewed commit: 680ec7d</sub> <!-- greptile_other_comments_section --> <sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs