Skip to content

fix(terminal): correct attachExisting reentrancy supersession check (W3)#246

Merged
JasonYeYuhe merged 1 commit into
mainfrom
fix/w3-terminal-reentrancy
Jun 28, 2026
Merged

fix(terminal): correct attachExisting reentrancy supersession check (W3)#246
JasonYeYuhe merged 1 commit into
mainfrom
fix/w3-terminal-reentrancy

Conversation

@JasonYeYuhe

Copy link
Copy Markdown
Owner

What & why

Follow-up to #243/#244/#245. TerminalSessionAdapter.attachExisting(to:sessionId:) subscribes to the live raw stream, awaits the tail snapshot, then paints. To stop a rapid re-attach to a different session from clobbering the in-flight one, it checked if Task.isCancelled { return } after the await — but it never stores or cancels its own Task, so Task.isCancelled was always false. A reconnect to session B while A's snapshot was in flight flushed A's stale snapshot into B's freshly-installed reattach buffer → garbled/duplicated terminal output. (Gemini deep-review 🔴.)

Fix

The adapter is @MainActor, so the snapshot await is the only suspension point and state after resume is the authoritative supersession signal. Replaced the dead check with a pure nonisolated static predicate:

nonisolated static func reattachSuperseded(state: State, targetSessionId: String) -> Bool {
    if case let .running(sid) = state { return sid != targetSessionId }
    return true
}

attachExisting bails before painting iff reattachSuperseded(...). nonisolated + pure so it's unit-tested without a WKWebView (the XCTest host aborts on WKWebView creation outside an AppKit loop — same reason render is a manual gate).

Tests

TerminalSessionAdapterTests (W3): same running session → not superseded; different running session → superseded; every non-running state → superseded. swift test full: 1825 tests, 0 failures.

Deferred (P2, per reviewers)

🤖 Generated with Claude Code

attachExisting() guarded against a rapid re-attach to a different session with
`if Task.isCancelled { return }` after awaiting the tail snapshot — but it never
stored/cancelled its own Task, so isCancelled was ALWAYS false. A reconnect to
session B while A's snapshot was in flight flushed A's stale snapshot into B's
freshly-installed reattach buffer (garbled/duplicated output). Gemini deep-review
catch.

The adapter is @mainactor, so `state` after the await is the authoritative
supersession signal. Replaced the dead check with a pure nonisolated static
reattachSuperseded(state:targetSessionId:) predicate (superseded unless state is
still .running for the same id), unit-tested without a WKWebView.

swift test full: 1825 tests, 0 failures.

Deferred (P2, per reviewers): window.pushChunk WKContentWorld isolation — needs
an index.html/TerminalView restructure + on-device render validation; the nav
guard already blocks off-bundle navigation. Helper-not-running UX already shipped
in W1-B (#244).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 33d6d341b0

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

Comment on lines +161 to 164
if Self.reattachSuperseded(state: state, targetSessionId: sessionId) {
return sessionId
}
paintReattachSnapshot(snapshot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve cancellation for same-session reattach

TerminalAttachSession.doAttach cancels the previous attachTask before every reconnect, but reconnects reuse the same sessionId. If the canceled attach's getTailSnapshot resumes after the new same-session attach has set state back to .running(sessionId:), this state-only guard treats the canceled attach as current and falls through to paintReattachSnapshot, flushing the newer reattachBuffer before its own snapshot arrives. That reintroduces interleaved/garbled terminal output on same-session reconnects; keep Task.isCancelled (or an attach generation token) in addition to the session-id check.

Useful? React with 👍 / 👎.

@JasonYeYuhe JasonYeYuhe merged commit f9258c3 into main Jun 28, 2026
21 of 23 checks passed
@JasonYeYuhe JasonYeYuhe deleted the fix/w3-terminal-reentrancy branch June 28, 2026 14:07
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