fix(desktop): mark transcription connected on websocket open#9071
Merged
eulicesl merged 3 commits intoJul 5, 2026
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
2 issues found across 3 files
Confidence score: 3/5
- In
desktop/macos/Desktop/Sources/TranscriptionService.swift, reconnect logic can schedule overlapping retries whenonClosefires before handshake while a prior 10s timeout is still pending, which can over-count backoff and cause unstable or delayed recovery for transcription sessions — cancel/dedupe stale reconnect timers and ensure only one reconnect path can be active before merging. - In
desktop/macos/changelog/unreleased/20260705-transcription-connect-state.json, the entry format does not follow the required “Fixed/Added/Improved” convention, which risks release-note inconsistency — rename it to start with “Fixed …” before merge.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a desktop transcription connection-state race by making TranscriptionService mark the WebSocket as connected based on the actual WebSocket open/handshake event (via URLSessionWebSocketDelegate) rather than a fixed timer after resume().
Changes:
- Introduces a
URLSessionWebSocketDelegate(WebSocketConnectionDelegate) and wiresisConnected+ watchdog startup todidOpenWithProtocol. - Routes WebSocket close events through the existing connected vs pre-handshake reconnect paths, while ignoring stale delegate callbacks.
- Adds a focused regression test file plus a desktop changelog fragment for the behavior change.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| desktop/macos/Desktop/Tests/TranscriptionConnectionStateTests.swift | Adds regression coverage for the “connected is driven by WebSocket open” behavior. |
| desktop/macos/Desktop/Sources/TranscriptionService.swift | Moves connected-state transition to the WebSocket delegate open callback; adds delegate and cleanup. |
| desktop/macos/changelog/unreleased/20260705-transcription-connect-state.json | Documents the user-facing behavior change in connection status reporting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a desktop transcription connection-state bug where
TranscriptionServicemarked the WebSocket as connected from a fixed 0.5s timer afterresume(), rather than from the WebSocket handshake/open event.Bug
When cloud transcription starts, the app could briefly treat the stream as connected before the WebSocket had actually opened. That could allow audio send/connected callbacks to run against a task that was only in the pre-handshake phase.
Root Cause
TranscriptionService.connectToBackendcreated a plainURLSession, calledwebSocketTask.resume(), then setisConnected = truefromDispatchQueue.main.asyncAfter(deadline: .now() + 0.5)if the task state looked running.URLSessionWebSocketTask.State.runningis not the same as the server-side WebSocket open event.Fix
URLSessionWebSocketDelegatefor the transcription WebSocket session.isConnected = true, reconnect-attempt reset, watchdog start, andonConnectedcallback into the delegatedidOpenWithProtocolpath.webSocketTask.webSocketTask, so a stale timeout from an older attempt cannot interrupt a newer reconnect attempt.Testing
Before fix:
TranscriptionService must not mark the WebSocket connected from a fixed timerTranscriptionService should install a URLSessionWebSocketDelegateTranscriptionService should mark connected from URLSessionWebSocketDelegate.didOpenWithProtocolAfter fix:
xcrun swift test --filter TranscriptionConnectionStateTests --package-path Desktoppassed: 2 tests, 0 failurespython3 .github/scripts/desktop-changelog.py validatepassedgit diff --checkpassedxcrun swift build -c debug --package-path Desktoppassed./scripts/agent-logic-harness.shpassed all sections:Review follow-up after Cubic comments:
Fixed.xcrun swift test --filter TranscriptionConnectionStateTests --package-path Desktop: passed, 2 tests, 0 failures.python3 .github/scripts/desktop-changelog.py validate: passed.git diff --check: passed.xcrun swift build -c debug --package-path Desktop: passed../scripts/agent-logic-harness.sh: passed.Second review follow-up after Copilot comments:
onConnectedrun, preserving the previous callback queue behavior.xcrun swift test --filter TranscriptionConnectionStateTests --package-path Desktop: passed, 2 tests, 0 failures.python3 .github/scripts/desktop-changelog.py validate: passed.git diff --check: passed.xcrun swift build -c debug --package-path Desktop: passed../scripts/agent-logic-harness.sh: passed.Visual Proof
No useful screenshot for this one: the bug is an internal transport-state race before/around the WebSocket handshake. The strongest evidence is the failing-before/passing-after focused regression plus debug build and desktop harness output.
Risk
Low. The change keeps existing reconnect methods and only changes the source of truth for the initial connected transition from a timer to the system WebSocket open callback.
Rollback
Revert this commit to restore the previous timer-based connection behavior.