Commit 382caaa
* fix(desktop): robust WebSocket reconnection in TranscriptionService
Fixes #6193 — 64K Sentry events from WebSocket transcription disconnects.
Root causes fixed:
- Race condition: replaced 0.5s hardcoded delay with URLSessionWebSocketDelegate
handshake detection (didOpenWithProtocol) + 10s connect timeout
- Audio loss: added ring buffer (960KB/30s TTL) to hold audio during reconnect,
replayed on successful reconnection
- Permanent failure: removed 10-attempt reconnect cap, now retries indefinitely
with exponential backoff + jitter (max 60s) while recording is active
- Thread safety: all mutable connection state behind serial DispatchQueue,
ConnectionState enum replaces bare Bool
- Stale callbacks: generation token discards delegate callbacks from old connections
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): graceful WebSocket close forwarding in proxy
Part of #6193 — when one side of the Deepgram WS proxy disconnects,
forward a close frame to the other side with a 5s timeout instead of
abruptly dropping both connections. Prevents "Connection reset by peer"
errors on the Swift client.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(desktop): changelog entry for WebSocket reconnect fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): address review — gate auth on generation, idempotent disconnect
Review cycle fixes for #6201:
- Gate proxy auth Task and connectWithAuth on generation + shouldReconnect
to prevent zombie connections after stop()
- Make handleDisconnection idempotent: only transitions from .connected
or .connecting states, preventing duplicate onDisconnected notifications
and inflated reconnect counts from concurrent failure callbacks
- Validate generation in didOpenWithProtocol to reject stale handshakes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): bump generation on teardown, salvage partial audio buffer
Review cycle 2 fixes for #6201:
- Bump _connectionGeneration in both disconnect() and handleDisconnection()
so in-flight receiveMessage/keepalive callbacks are invalidated, preventing
stale transcript delivery after stop() or during reconnect gap
- Salvage partial audioBuffer contents into reconnectBuffer on disconnect,
preventing the last ~100ms audio chunk from being lost or replayed
out of order after reconnection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): re-buffer unsent chunks on replay failure
Review cycle 3 fix for #6201:
- On replay send error, re-buffer the failed chunk and all remaining
chunks back into reconnectBuffer, then trigger handleDisconnection()
to reconnect and retry. Previously, drained chunks were permanently
lost if the socket failed during replay.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(desktop): expose ring buffer and backoff for testability
Extract reconnectDelay() as static method and make
ReconnectAudioRingBuffer internal for @testable import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(desktop): add unit tests for ring buffer and backoff calculation
13 tests covering:
- ReconnectAudioRingBuffer: append/drain, TTL eviction, byte-cap
eviction, oversize chunk truncation, prune, empty data handling
- reconnectDelay(): exponential growth, max backoff cap, jitter bounds,
attempt zero edge case
All 13 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): update OnboardingFlowTests for new migratedStep params
Add missing hasRemovedNotificationStep, hasInsertedFloatingBarShortcutStep,
and hasMigratedPagedIntro parameters to fix pre-existing compile error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): update OnboardingFlowTests for current 17-step flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): unwind state on invalid URL, sequential replay to prevent duplicates
- Invalid URL guards in connectWithAuth now call handleDisconnection() instead
of bare return, preventing permanent .connecting wedge state
- Replay sends chunks sequentially (callback-chained) so only the first failure
re-buffers remaining chunks, preventing duplicate audio from concurrent failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(desktop): add test accessors for state machine verification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(desktop): add state machine, idempotency, and URL construction tests
- 7 TranscriptionServiceStateTests: initial state, stop transitions,
handleDisconnection idempotency from all 4 states
- 3 URLConstructionTests: empty base, malformed base, valid base
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): add hasReorderedTrustStep param to OnboardingFlowTests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): prevent replay interleaving and cap backoff at 32s
Add _isReplaying flag to gate live sendAudio() calls during buffered
chunk replay — prevents interleaving that could corrupt transcript order.
Cap jitter range to 0.8...1.0 and clamp final delay to maxBackoff (32s)
so reconnect never exceeds documented maximum.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): correct OnboardingFlowTests step order to match main
Update expected step order to Name, Language, Trust (matching current
OnboardingFlow.steps after trust step reorder on main).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(desktop): drain chunks accumulated during replay to prevent stranding
After replayChunksSequentially finishes the initial batch, check if
sendAudio() appended new data to reconnectBuffer while _isReplaying
was true. If so, drain and continue replaying before clearing the flag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(desktop): add test accessors for replay gating and reconnect buffer
Add testIsReplaying, testSetIsReplaying, testAppendToReconnectBuffer,
and testDrainReconnectBuffer accessors for @testable import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(desktop): add replay gating and disconnect buffer salvage tests
Test that sendAudio buffers data in reconnectBuffer during replay,
does not buffer when not replaying, _isReplaying flag initializes
correctly, and reconnect buffer survives handleDisconnection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(desktop): add ProxyCloseOrigin enum variant test
Verify all four ProxyCloseOrigin variants exist with distinct Debug
output, covering the new close-origin tracking in proxy_ws_bidirectional.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Simplify WS reconnect fix: remove audio buffering, keep connection state management
Remove ReconnectAudioRingBuffer, replay logic, and _isReplaying gating.
Audio is now silently dropped during disconnects (buffering is a future phase).
Keep: thread-safe ConnectionState, URLSessionWebSocketDelegate handshake,
infinite reconnect with backoff+jitter, idempotent handleDisconnection,
generation tokens for stale callback discard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove ring buffer and replay tests, add sendAudio drop tests
Remove ReconnectAudioRingBufferTests, ReplayGatingTests, and
DisconnectBufferSalvageTests. Add SendAudioDropTests verifying audio
is silently dropped in disconnected/reconnecting/connecting states.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update changelog to remove audio buffering mention
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a29220e commit 382caaa
4 files changed
Lines changed: 561 additions & 116 deletions
File tree
- desktop
- Backend-Rust/src/routes
- Desktop
- Sources
- Tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
253 | 253 | | |
254 | 254 | | |
255 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
256 | 265 | | |
| 266 | + | |
257 | 267 | | |
258 | 268 | | |
259 | 269 | | |
| |||
278 | 288 | | |
279 | 289 | | |
280 | 290 | | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
290 | 307 | | |
291 | | - | |
292 | | - | |
293 | | - | |
| 308 | + | |
294 | 309 | | |
295 | 310 | | |
| 311 | + | |
296 | 312 | | |
297 | 313 | | |
298 | 314 | | |
299 | 315 | | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
309 | 333 | | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
| 334 | + | |
314 | 335 | | |
315 | 336 | | |
| 337 | + | |
316 | 338 | | |
317 | 339 | | |
318 | | - | |
319 | | - | |
320 | | - | |
321 | | - | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
322 | 356 | | |
323 | 357 | | |
324 | 358 | | |
| |||
555 | 589 | | |
556 | 590 | | |
557 | 591 | | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
558 | 615 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
3 | 5 | | |
4 | 6 | | |
5 | 7 | | |
| |||
103 | 105 | | |
104 | 106 | | |
105 | 107 | | |
106 | | - | |
| 108 | + | |
107 | 109 | | |
108 | 110 | | |
109 | 111 | | |
| |||
0 commit comments