Repository navigation
🤖 refactor: the terminal router keeps its sessions across a reconnect - #6304
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64b6aeb51f
ℹ️ 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".
64b6aeb to
c311093
Compare
c4c5f06 to
976b0f4
Compare
|
@codex review |
Dogfood: terminal across hard connection drops (head c4c5f06)Result: pass. Setup:
For comparison, origin/main 924f7dc repeats lines after one drop: the buffer grew from 213 to 360 lines, and 143 of the numbers 1..200 appeared twice. The old PR head 64b6aeb broke in 1 of 4 drops with the ghostty-web use-after-free that c4c5f06 fixes. After the reconnect (this PR): Known difference, low severity: after a full server restart, the pop-out window of the now dead session loses its scrollback before it shows "[Process exited with code 0]" (main keeps the old screen). I track it for PR 6's dogfood run. The later commit 976b0f4 (live client only, resize before attach) is covered by unit tests in both gap modes. PR 6 reruns the full dogfood. Video (one screenshot per second, 150 s): ./stable3-c4c5f062f3-reconnect.mp4 Generated with stable3-c4c5f062f3-reconnect.mp4 |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4fb7f38c6f
ℹ️ 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".
|
@codex review |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01a0dd8e4f
ℹ️ 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".
|
@codex review |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01a0dd8e4f
ℹ️ 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".
…ient after a reconnect
…inal.reset() (use-after-free in ghostty-web)
…for the replayed resize
…a restarted server sends "" for a session it does not know)
…gain (an empty snapshot can be a real blank screen)
01a0dd8 to
0aec776
Compare

Summary
This PR is layer 3 of the reconnect stack (base: #6295). An open terminal now keeps its screen and scrollback when the connection drops and comes back. Today the provider builds a new
TerminalSessionRouterfor each client and passesnullin the gap. EachTerminalViewthen subscribes again and callsterm.clear(), and the reattach repeats the scrollback lines.Implementation
TerminalRouterProvidercreates one router per mount (and per popout mode). It callsrouter.setClient()with the live client (useLiveAPI()), so a reconnecting state that keeps the old client still reaches the router as offline. The router is nevernullafter the first commit, soTerminalViewkeeps its one subscription.router.setClient(client)keeps every session, subscriber and cached screen. For each live session it:attach, because the firstattachmessage is the server's screen at the PTY size;attachandonExiton the new client.Exited sessions are not restarted. Popouts attach as popouts again.
setClient(null)stops the streams and keeps everything else. Offline input is dropped with a debug log, and offline resizes are kept.Supersede and recheck after await. Each stream checks that its session object and generation are still current before it delivers. So an
attachthat resolves after a newer client arrived delivers nothing.Reattach replaces the screen. The server sends the full screen with its scrollback. So the first screen after a reattach comes with
{ replace: true }, andTerminalViewwrites a VT full reset (ESC c) and the screen in one write. It does not callTerminal.reset(): in ghostty-web that frees the WASM terminal that the selection and mouse code still use (memory access out of bounds). A subscriber that showed nothing yet gets noreplace.ConnectionLostErroris not logged as a stream error, andsendInputno longer leaves an unhandled rejection when it fails.To stay inside the CRAP gate, I split
startStreaminto one method per stream loop, extractedwriteScreenState()fromTerminalView, and keep the sizes in their own map.Validation
TerminalSessionRouter.test.ts: restart on the new client and supersede of the old stream, late attach after a newer client, offline input and size, attach waits for the replayed resize (success and failure), exited sessions, popout, no replace without an earlier screen.TerminalRouterContext.test.tsx(both gap modes): one router and one subscription across a drop and a reconnect, a resize in the gap reaches only the new server, and a reattach on the new client. The kept-gap mode fails with the old provider.writeScreenState.test.tsruns the real ghostty WASM: a replace leaves one copy of each line and keeps the terminal usable.Review record
ConnectionLostError. Needed once the provider keeps the last client while reconnecting.Known limitation
After a full server restart, the server no longer knows the session and sends an empty screen. The pop-out of that dead session replaces its last screen with it, then shows the exit message. The main window removes the tab, as on main.
Risks
Medium, limited to the terminal. The full reset relies on the server's screen state to restore the terminal modes. The hands-on run checked prompt colors, cursor row and tab title.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$55.49