[LUM-681] Fix audio tap format mismatch by resetting engine before installTap#23766
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| inputNode.removeTap(onBus: 0) | ||
| audioEngine.stop() | ||
| audioEngine.reset() | ||
|
|
||
| let format = inputNode.outputFormat(forBus: 0) | ||
| guard format.channelCount > 0, format.sampleRate > 0 else { | ||
| log.error("Invalid audio format — channels: \(format.channelCount), sampleRate: \(format.sampleRate)") | ||
| return false | ||
| } | ||
|
|
||
| inputNode.removeTap(onBus: 0) | ||
| inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: nil, block: block) | ||
| inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: format, block: block) |
There was a problem hiding this comment.
🚩 Behavioral change from nil format to explicit format in installTap
The PR changes installTap(onBus:bufferSize:format:block:) from passing nil (line 104 old) to passing the explicitly queried format (line 115 new). Previously, the old code's comment explained that nil lets AVAudioEngine use its own internal hardware format, which is "always self-consistent." The new approach resets the engine first to flush stale cached state, then queries the fresh format and passes it explicitly. This is a valid strategy — the reset() call forces the engine to re-read hardware state, so the subsequent outputFormat(forBus:) should return the current hardware format. However, the comment at lines 79-80 notes that both outputFormat(forBus:) and nil format resolve to the same (potentially stale) value. If reset() truly flushes the cache as intended, passing the explicit format should be equivalent to nil but with the advantage of being verifiable via the guard check at line 110. If reset() does not fully flush the cached format in all edge cases (e.g., rapid successive audio route changes), the explicit format could still diverge from the engine's internal hardware format, reintroducing the crash the PR aims to fix. This is worth validating with testing on real hardware route changes (Bluetooth/AirPods).
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Agreed this warrants hardware testing — CI doesn't run macOS builds, so the format mismatch can only be reproduced with real audio route changes (Bluetooth/AirPods connect/disconnect). The key bet is that reset() flushes the stale format cache before outputFormat(forBus:) is called. This matches the pattern used in Apple's SpokenWord sample and the fix in expo-audio-stream PR #220 which resolved the same crash in production.
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
…stallTap After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the format cached inside AVAudioInputNode diverges from the engine's actual hardware format. Both outputFormat(forBus:) and a nil format argument to installTap resolve to this stale value, causing: 'Failed to create tap due to format mismatch, <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>' Fix: call audioEngine.reset() before re-querying the format, then pass it explicitly to installTap. This forces the engine to discard its cached graph state and re-read the hardware, so the tap, node, and engine all agree. Co-Authored-By: tkheyfets <timur@vellum.ai>
c021286 to
7a62bf7
Compare
…stallTap (#23766) After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the format cached inside AVAudioInputNode diverges from the engine's actual hardware format. Both outputFormat(forBus:) and a nil format argument to installTap resolve to this stale value, causing: 'Failed to create tap due to format mismatch, <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>' Fix: call audioEngine.reset() before re-querying the format, then pass it explicitly to installTap. This forces the engine to discard its cached graph state and re-read the hardware, so the tap, node, and engine all agree. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai>
* revert: disable Teleport feature flag by default (#23744) (#23815) * fix: replace auxWhite-on-primaryBase with VButton across the app (#23802) * fix: use VButton for inline surface action buttons Replace raw Button with manual color functions in InlineSurfaceRouter with the design system VButton component. The manual buttonForeground used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which resolves to #FDFDFC in dark mode, producing invisible white-on-white text. Closes LUM-730 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix: replace auxWhite-on-primaryBase with VButton in additional locations FileUploadSurfaceView: Upload/Cancel buttons used raw Button with VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode. Replaced with VButton(.primary) and VButton(.outlined). JITPermissionView: Permission buttons used the same auxWhite pattern. Replaced with VButton(.primary/.outlined, isFullWidth: true). ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on primaryBase fill. Changed to VColor.contentInset which adapts per color scheme. ChatGallerySection: Gallery demo of surface action pills mirrored the old buggy pattern. Updated to use VButton so the gallery accurately represents production rendering. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * Make dictation engine start non-blocking with audio route resilience (#23811) * Make dictation engine start non-blocking and improve audio resilience - Add installTapAndStartAsync to AudioEngineController for non-blocking engine start using Swift concurrency (withCheckedContinuation) - Extract installTapAndStartImpl to share logic between sync/async paths - Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode after Bluetooth device connect/disconnect and AirPods mode switches - Restructure VoiceInputManager.beginRecording() to show recording UI and play activation chime immediately, then start engine async via Task - Move DictationContextCapture off the critical path: engine starts concurrently on its audio queue while context capture runs on main - Add SFSpeechRecognizer transient unavailability retry (recreate if isAvailable returns false after sleep/wake or heavy use) - Handle edge case where PTT is released before async engine start completes (stopRecordingForDictation cleans up directly) Co-Authored-By: tkheyfets <timur@vellum.ai> * Tear down engine when async startup outlives recording session When PTT is released before installTapAndStartAsync completes, the isRecording guard now stops and removes the tap if the engine started successfully, preventing the mic path from staying alive with no active recording session. Co-Authored-By: tkheyfets <timur@vellum.ai> * Add recording generation token and gate context capture on start success Co-Authored-By: tkheyfets <timur@vellum.ai> * Guard stale teardown against active sessions and gate rewarm on mic auth Co-Authored-By: tkheyfets <timur@vellum.ai> * Move context capture to Task.detached to avoid blocking main actor Co-Authored-By: tkheyfets <timur@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * [LUM-681] Fix audio tap format mismatch by resetting engine before installTap (#23766) After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the format cached inside AVAudioInputNode diverges from the engine's actual hardware format. Both outputFormat(forBus:) and a nil format argument to installTap resolve to this stale value, causing: 'Failed to create tap due to format mismatch, <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>' Fix: call audioEngine.reset() before re-querying the format, then pass it explicitly to installTap. This forces the engine to discard its cached graph state and re-read the hardware, so the tap, node, and engine all agree. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * fix: pass transport hints through HTTP message endpoint for managed-mode conversations (#23824) * fix: pass transport metadata through POST /v1/messages to enable host environment hints The HTTP message handler auto-creates conversations without transport metadata, so applyTransportMetadata() returns early and host environment hints (hostHomeDir, hostUsername) are never injected into the LLM context. This causes the assistant to hallucinate the user's home directory path from their display name instead of using the actual macOS username. Thread transport metadata from the message request body through SendMessageDeps.getOrCreateConversation() to the daemon, and send hostHomeDir/hostUsername from the macOS client in every message request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace dynamic imports with static type imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
||
| inputNode.removeTap(onBus: 0) | ||
| inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: nil, block: block) | ||
| inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: format, block: block) |
There was a problem hiding this comment.
🚩 Stale call-site comments now incorrectly describe installTapAndStart behavior
The two call sites in OpenAIVoiceService.swift at lines 224-226 and 447-449 both say:
// Passes nil for format so AVAudioEngine uses its internal hardware
// format, preventing sampleRate mismatch crashes.
After this PR, installTapAndStartImpl no longer passes nil — it resets the engine and passes an explicit format queried from outputFormat(forBus: 0). These comments are now factually wrong and could mislead developers debugging audio format issues. The clients/AGENTS.md comment quality rule says comments must describe the code's intent and behavior. These should be updated to reflect the new reset-then-explicit-format strategy.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it. |
* revert: disable Teleport feature flag by default (#23744) (#23815) * fix: replace auxWhite-on-primaryBase with VButton across the app (#23802) * fix: use VButton for inline surface action buttons Replace raw Button with manual color functions in InlineSurfaceRouter with the design system VButton component. The manual buttonForeground used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which resolves to #FDFDFC in dark mode, producing invisible white-on-white text. Closes LUM-730 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix: replace auxWhite-on-primaryBase with VButton in additional locations FileUploadSurfaceView: Upload/Cancel buttons used raw Button with VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode. Replaced with VButton(.primary) and VButton(.outlined). JITPermissionView: Permission buttons used the same auxWhite pattern. Replaced with VButton(.primary/.outlined, isFullWidth: true). ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on primaryBase fill. Changed to VColor.contentInset which adapts per color scheme. ChatGallerySection: Gallery demo of surface action pills mirrored the old buggy pattern. Updated to use VButton so the gallery accurately represents production rendering. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * Make dictation engine start non-blocking with audio route resilience (#23811) * Make dictation engine start non-blocking and improve audio resilience - Add installTapAndStartAsync to AudioEngineController for non-blocking engine start using Swift concurrency (withCheckedContinuation) - Extract installTapAndStartImpl to share logic between sync/async paths - Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode after Bluetooth device connect/disconnect and AirPods mode switches - Restructure VoiceInputManager.beginRecording() to show recording UI and play activation chime immediately, then start engine async via Task - Move DictationContextCapture off the critical path: engine starts concurrently on its audio queue while context capture runs on main - Add SFSpeechRecognizer transient unavailability retry (recreate if isAvailable returns false after sleep/wake or heavy use) - Handle edge case where PTT is released before async engine start completes (stopRecordingForDictation cleans up directly) Co-Authored-By: tkheyfets <timur@vellum.ai> * Tear down engine when async startup outlives recording session When PTT is released before installTapAndStartAsync completes, the isRecording guard now stops and removes the tap if the engine started successfully, preventing the mic path from staying alive with no active recording session. Co-Authored-By: tkheyfets <timur@vellum.ai> * Add recording generation token and gate context capture on start success Co-Authored-By: tkheyfets <timur@vellum.ai> * Guard stale teardown against active sessions and gate rewarm on mic auth Co-Authored-By: tkheyfets <timur@vellum.ai> * Move context capture to Task.detached to avoid blocking main actor Co-Authored-By: tkheyfets <timur@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * [LUM-681] Fix audio tap format mismatch by resetting engine before installTap (#23766) After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the format cached inside AVAudioInputNode diverges from the engine's actual hardware format. Both outputFormat(forBus:) and a nil format argument to installTap resolve to this stale value, causing: 'Failed to create tap due to format mismatch, <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>' Fix: call audioEngine.reset() before re-querying the format, then pass it explicitly to installTap. This forces the engine to discard its cached graph state and re-read the hardware, so the tap, node, and engine all agree. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * fix: pass transport hints through HTTP message endpoint for managed-mode conversations (#23824) * fix: pass transport metadata through POST /v1/messages to enable host environment hints The HTTP message handler auto-creates conversations without transport metadata, so applyTransportMetadata() returns early and host environment hints (hostHomeDir, hostUsername) are never injected into the LLM context. This causes the assistant to hallucinate the user's home directory path from their display name instead of using the actual macOS username. Thread transport metadata from the message request body through SendMessageDeps.getOrCreateConversation() to the daemon, and send hostHomeDir/hostUsername from the macOS client in every message request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace dynamic imports with static type imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Release v0.6.1 * Cherry-pick fixes for v0.6.1 (#23785) * Increase teleport import timeout from 2 to 5 minutes (#23749) * increase teleport import timeout from 2 to 5 minutes * fix: update platform import timeout error message to say 5 minutes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update billing tab copy: referral subtitle, remove earning cap note, move credit info to card subtitle (#23751) * fix(macos): always collapse thinking blocks by default (#23750) Thinking blocks were auto-expanding during streaming, showing a wall of text. Remove the auto-expand logic so blocks always start collapsed. Users can still manually expand them. The header already shows "Thinking..." vs "Thought process" as a streaming indicator. Closes LUM-729 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * [LUM-684/LUM-726] Fix dictation crash: pass nil format to installTap (#23754) * Fix dictation crash: pass nil format to installTap, consolidate audio engine calls Pass nil for the format parameter in AVAudioNode.installTap(onBus:bufferSize:format:block:) so AVAudioEngine uses its own internal hardware format, which is always self-consistent. This prevents NSInternalInconsistencyException crashes caused by format.sampleRate != hwFormat.sampleRate when the cached format from outputFormat(forBus:) diverges from the engine's internal hardware format after audio route changes (Bluetooth, USB mic, AirPods mode switch). AudioEngineController.swift: - installTapAndStart() now passes nil instead of explicit format to installTap - Removed 6 now-unused methods: inputNodeFormat(), installTap(bufferSize:format:block:), removeTap(), prepare(), start(), prepareAndStart() OpenAIVoiceService.swift: - startRecording(): replaced separate inputNodeFormat/installTap/prepare/start chain with single installTapAndStart() call - startBargeInMonitor(): same migration to installTapAndStart() - Removed error-path removeTap() call (handled internally by installTapAndStart) Resolves: LUM-684, LUM-726 Co-Authored-By: tkheyfets <timur@vellum.ai> * fix: use explicit block: parameter in guard statements for installTapAndStart Swift doesn't support trailing closure syntax with guard statements, causing compilation errors. Use explicit block: parameter label instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace contrast buttons with primary style (#23753) Remove all production usages of .contrast button style in favor of .primary. Fixes white-on-white button visibility issues in chat composer. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Inject host environment via transport hints (#23779) * refactor: discriminated union for transport metadata, remove iOS proxy setup (#23776) * feat: inject interface ID and macOS host environment into transport hints (#23777) * feat: send hostHomeDir and hostUsername from macOS client (#23778) * fix: remove iOS from proxy restoration in conversation-process.ts (#23782) --------- Co-authored-by: Carson Shaar <carson.s.shaar@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com> * [skip ci] Cherry-pick fixes for v0.6.1 (#23820) * revert: disable Teleport feature flag by default (#23744) (#23815) * fix: replace auxWhite-on-primaryBase with VButton across the app (#23802) * fix: use VButton for inline surface action buttons Replace raw Button with manual color functions in InlineSurfaceRouter with the design system VButton component. The manual buttonForeground used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which resolves to #FDFDFC in dark mode, producing invisible white-on-white text. Closes LUM-730 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> * fix: replace auxWhite-on-primaryBase with VButton in additional locations FileUploadSurfaceView: Upload/Cancel buttons used raw Button with VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode. Replaced with VButton(.primary) and VButton(.outlined). JITPermissionView: Permission buttons used the same auxWhite pattern. Replaced with VButton(.primary/.outlined, isFullWidth: true). ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on primaryBase fill. Changed to VColor.contentInset which adapts per color scheme. ChatGallerySection: Gallery demo of surface action pills mirrored the old buggy pattern. Updated to use VButton so the gallery accurately represents production rendering. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * Make dictation engine start non-blocking with audio route resilience (#23811) * Make dictation engine start non-blocking and improve audio resilience - Add installTapAndStartAsync to AudioEngineController for non-blocking engine start using Swift concurrency (withCheckedContinuation) - Extract installTapAndStartImpl to share logic between sync/async paths - Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode after Bluetooth device connect/disconnect and AirPods mode switches - Restructure VoiceInputManager.beginRecording() to show recording UI and play activation chime immediately, then start engine async via Task - Move DictationContextCapture off the critical path: engine starts concurrently on its audio queue while context capture runs on main - Add SFSpeechRecognizer transient unavailability retry (recreate if isAvailable returns false after sleep/wake or heavy use) - Handle edge case where PTT is released before async engine start completes (stopRecordingForDictation cleans up directly) Co-Authored-By: tkheyfets <timur@vellum.ai> * Tear down engine when async startup outlives recording session When PTT is released before installTapAndStartAsync completes, the isRecording guard now stops and removes the tap if the engine started successfully, preventing the mic path from staying alive with no active recording session. Co-Authored-By: tkheyfets <timur@vellum.ai> * Add recording generation token and gate context capture on start success Co-Authored-By: tkheyfets <timur@vellum.ai> * Guard stale teardown against active sessions and gate rewarm on mic auth Co-Authored-By: tkheyfets <timur@vellum.ai> * Move context capture to Task.detached to avoid blocking main actor Co-Authored-By: tkheyfets <timur@vellum.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * [LUM-681] Fix audio tap format mismatch by resetting engine before installTap (#23766) After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the format cached inside AVAudioInputNode diverges from the engine's actual hardware format. Both outputFormat(forBus:) and a nil format argument to installTap resolve to this stale value, causing: 'Failed to create tap due to format mismatch, <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>' Fix: call audioEngine.reset() before re-querying the format, then pass it explicitly to installTap. This forces the engine to discard its cached graph state and re-read the hardware, so the tap, node, and engine all agree. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> * fix: pass transport hints through HTTP message endpoint for managed-mode conversations (#23824) * fix: pass transport metadata through POST /v1/messages to enable host environment hints The HTTP message handler auto-creates conversations without transport metadata, so applyTransportMetadata() returns early and host environment hints (hostHomeDir, hostUsername) are never injected into the LLM context. This causes the assistant to hallucinate the user's home directory path from their display name instead of using the actual macOS username. Thread transport metadata from the message request body through SendMessageDeps.getOrCreateConversation() to the daemon, and send hostHomeDir/hostUsername from the macOS client in every message request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace dynamic imports with static type imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: reset non-version-bump files to match main Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Noa Flaherty <noa@vellum.ai> Co-authored-by: Carson Shaar <carson.s.shaar@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com> Co-authored-by: David Vargas Fuertes <vargasvellum@Davids-MacBook-Pro.local>
Fixes the
"Failed to create tap due to format mismatch"crash by callingstop()→removeTap()→reset()before re-querying the hardware format and passing it explicitly toinstallTap. After audio-route changes (Bluetooth, USB mic, AirPods),AVAudioInputNodecaches a stale format that bothoutputFormat(forBus:)and anilformat argument resolve to —reset()forces the engine to discard that cache and re-read the hardware, matching Apple's sample code pattern. The fix lives ininstallTapAndStartImpl, covering both the synchronous and async tap+start paths.Review & Testing Checklist for Human
stop()+reset()calls run on the dedicated audio serial queue before everyinstallTap. Confirm hold-Fn → recording still feels instant, especially afterprewarm()has run.installTapAndStartAsync): PTT dictation now uses the async variant — confirm dictation still starts promptly and records correctly after Bluetooth route changes.Recommended test plan: On a Mac with Bluetooth headphones: (1) start a voice recording, (2) connect/disconnect Bluetooth mid-session or between sessions, (3) start another recording. Previously this would crash with the format mismatch error.
Notes
nilfor format) was an intentional attempt to fix this same class of bug, butnilinternally resolves to the same staleoutputFormat— so it was insufficient. The key insight is thatreset()is needed to flush the stale cache before any format query.stop→removeTap→resetperAGENTS.mdand Apple's SpokenWord sample.reset()does not fully flush the cached format in rare edge cases (e.g., rapid successive audio route changes), the explicit format could still diverge — worth validating during manual testing.Link to Devin session: https://app.devin.ai/sessions/50724a73a64f485684e6b65d49c12540
Requested by: @tkheyfets