Skip to content

fix(macos): guard SSE task against superseded session on MainActor race#25426

Merged
siddseethepalli merged 1 commit into
mainfrom
do/macos-sse-session-race
Apr 14, 2026
Merged

fix(macos): guard SSE task against superseded session on MainActor race#25426
siddseethepalli merged 1 commit into
mainfrom
do/macos-sse-session-race

Conversation

@siddseethepalli

Copy link
Copy Markdown
Contributor

Summary

  • fix: use dedicated URLSession per SSE connection to prevent EXC_BAD_ACCESS #25396 made each SSE connection own a dedicated URLSession and invalidates the previous session at the top of startSSEStream(). That fixed an iterator-vs-invalidation EXC_BAD_ACCESS, but introduced a new crash: when startSSEStream() is called twice in rapid succession on MainActor (which happens on boot — MainWindowView+Lifecycle and GatewayConnectionManager both call startSSE), the second call invalidates the session and cancels the prior task before that task has run its first instruction. When the cancelled task finally runs and calls session.bytes(for:), NSURLSession throws an uncatchable ObjC NSGenericException from -[__NSURLSessionLocal taskForClassInfo:], aborting the process (SIGABRT).
  • Adds a guard !Task.isCancelled, self.sseSession === session else { return } at the top of the SSE task body in EventStreamClient.startSSEStream, so a superseded task exits cleanly instead of invoking bytes(for:) on an invalidated session.
  • Scope-limited: no change to the session creation, invalidation order, reconnect logic, or call sites.

Original prompt

it

…bytes(for:)

PR #25396 introduced a race on MainActor: a second call to startSSEStream()
(which happens on boot via MainWindowView+Lifecycle and
GatewayConnectionManager both calling startSSE) invalidates the session and
cancels the first Task before its body has executed. When that cancelled
Task later runs and calls session.bytes(for:), NSURLSession throws an
NSGenericException from -[__NSURLSessionLocal taskForClassInfo:], which is
uncatchable in Swift and crashes the process with SIGABRT.

Guard the Task body with `Task.isCancelled` and an identity check on
`self.sseSession === session` before touching the session, so a superseded
Task exits cleanly instead of exploding inside URLSession.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@siddseethepalli

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ 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".

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.

1 participant