Skip to content

Make stopRecording() async to avoid reading incomplete WAV#585

Open
eherrerosj wants to merge 2 commits into
Beingpax:mainfrom
eherrerosj:fix/async-stop-recording
Open

Make stopRecording() async to avoid reading incomplete WAV#585
eherrerosj wants to merge 2 commits into
Beingpax:mainfrom
eherrerosj:fix/async-stop-recording

Conversation

@eherrerosj
Copy link
Copy Markdown

@eherrerosj eherrerosj commented Mar 13, 2026

Summary

  • Recorder.stopRecording() was fire-and-forget: it dispatched the stop to a serial audio queue and returned right away
  • The transcription pipeline could then try to read the WAV file before CoreAudioRecorder had finished closing it
  • Now async, using withCheckedContinuation to actually wait for the stop to finish

Test plan

  • Record a transcription, check it processes correctly
  • Rapid start/stop cycles — no deadlocks
  • Cancel mid-recording, confirm it still works

🤖 Generated with Claude Code


Summary by cubic

Make Recorder.stopRecording() async and wait for the hardware queue to finish so WAV files are fully closed before reading. Also fixes race conditions during cancel and rapid start/stop.

  • Bug Fixes

    • Made stopRecording() async with withCheckedContinuation and wait on audioSetupQueue; capture and clear recorder/onAudioChunk before awaiting to avoid reentrancy and stale cleanup.
    • Updated call sites to await the stop in Recorder and VoiceInkEngine; capture recordedFile before awaiting and only nil it if unchanged to keep cancel paths safe.
  • Migration

    • Callers must await recorder.stopRecording().

Written for commit 0478469. Summary will update on new commits.

The old fire-and-forget dispatch to the audio queue meant callers could
read the WAV file before CoreAudioRecorder finished closing it. Use
withCheckedContinuation to wait for the stop to complete on the serial
hardware queue before returning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="VoiceInk/Recorder.swift">

<violation number="1" location="VoiceInk/Recorder.swift:167">
P1: `stopRecording()` became reentrancy-unsafe: it awaits before clearing shared state, so interleaved `startRecording()` can be overwritten by stale cleanup.</violation>
</file>

<file name="VoiceInk/Whisper/VoiceInkEngine.swift">

<violation number="1" location="VoiceInk/Whisper/VoiceInkEngine.swift:144">
P1: Awaiting stop before clearing `recordedFile` creates a re-entrancy race that can wipe a newer recording URL during rapid cancel/restart.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread VoiceInk/Recorder.swift
Comment thread VoiceInk/Whisper/VoiceInkEngine.swift Outdated
Clear shared state (recorder, onAudioChunk) before the await in
stopRecording() so a concurrent startRecording() won't have its new
state overwritten by stale cleanup when the continuation resumes.

In VoiceInkEngine, capture recordedFile before awaiting stop and only
nil it out if it hasn't been replaced by a newer recording.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="VoiceInk/Recorder.swift">

<violation number="1" location="VoiceInk/Recorder.swift:166">
P1: `stopRecording()` applies unconditional post-await cleanup that can run after a new recording starts, causing incorrect recording state and media restoration mid-recording.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread VoiceInk/Recorder.swift
audioSetupQueue.async {
currentRecorder?.stopRecording()
}
recorder = nil
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: stopRecording() applies unconditional post-await cleanup that can run after a new recording starts, causing incorrect recording state and media restoration mid-recording.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At VoiceInk/Recorder.swift, line 166:

<comment>`stopRecording()` applies unconditional post-await cleanup that can run after a new recording starts, causing incorrect recording state and media restoration mid-recording.</comment>

<file context>
@@ -160,9 +160,14 @@ class Recorder: NSObject, ObservableObject {
+        // Capture and clear shared state before awaiting so a concurrent
+        // startRecording() won't have its new state overwritten when we resume.
         let currentRecorder = self.recorder
+        recorder = nil
+        onAudioChunk = nil
+
</file context>
Fix with Cubic

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.

2 participants