fix(app): suppress spurious close during intentional composite socket teardown#9438
Conversation
… teardown CompositeTranscriptionSocket.stop()/disconnect() tore down the child sockets while _status was still connected, so each child's onClosed re-entered _onSocketClosed as an "unexpected close": a misleading "Primary socket closed (code: null)" warning, a second disconnect of both children, and a false onClosed propagated to CaptureProvider (reconnect snackbar + keep-alive) on every intentional stop, e.g. each BLE device reconnect. Set _status = disconnected before touching the children so _onSocketClosed's guard treats their close callbacks as expected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f33c7d4b48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await Future.wait([primarySocket.stop(), secondarySocket.stop()]); | ||
|
|
||
| // Same ordering requirement as disconnect(). | ||
| _status = PureSocketStatus.disconnected; |
There was a problem hiding this comment.
Preserve final custom-STT transcripts during stop
When the primary is a custom STT socket, stop() is where the final transcript can be emitted: PurePollingSocket.disconnect() flushes buffered audio before closing, and PureStreamingSttSocket.disconnect() sends CloseStream then waits for final results. Setting the composite status to disconnected here makes _forwardAsSuggestedTranscript() return immediately, so those final suggested_transcript messages never reach the backend, which in custom_stt mode does not run STT on the raw audio. This loses the tail of recordings or settings-change reconnects whenever the primary emits a final result during stop; suppress the close callback without blocking primary messages until child stops finish.
Useful? React with 👍 / 👎.
Review pingSmall, targeted race fix in SymptomIntentional
FixSet Related (the perf win)Plugin context cache so each flap is cheap once model stays warm: BasedHardware/whisper_flutter_new#3 Would appreciate a maintainer pass when you have a moment. |
|
cc @beastoin @Git-on-my-level @mdmohsin7 — small race fix; companion perf PR is BasedHardware/whisper_flutter_new#3. |
|
Thanks for this clean, well-scoped fix — the race you identified is real. What I verified against The fix is correct: Setting One minor observation (not blocking): Would appreciate a maintainer pass before merge since this touches the composite socket lifecycle. The manual test plan looks solid for validating the BLE-flap scenario. |
kodjima33
left a comment
There was a problem hiding this comment.
mobile bug fix: suppress spurious socket close during intentional composite disconnect — approve only (needs-maintainer-review gate held for Nik)
Summary
CompositeTranscriptionSocket.stop()/disconnect()tore down child sockets while_statuswas stillconnected, so each child'sonClosedre-entered_onSocketClosedas an unexpected close:Primary socket closed (code: null)onClosedtoCaptureProvider→ reconnect snackbar + keep-alive churn on every intentional teardown (including every BLE reconnect)Fix
Set
_status = disconnectedbefore stopping/disconnecting children so_onSocketClosed's guard treats those callbacks as expected.Context
Seen with custom STT + on-device Whisper composite mode: device BLE flaps cause intentional composite teardown; the spurious close path made each flap look like a transcription outage and triggered unnecessary reconnect UI.
Related plugin work (context cache so reconnects are cheaper): BasedHardware/whisper_flutter_new#3
Test plan
Primary socket closed (code: null)warning for intentional stop