Skip to content

🤖 refactor: the terminal router keeps its sessions across a reconnect - #6304

Merged
ThomasK33 merged 6 commits into
mainfrom
stable-api/3-terminal-router
Oct 13, 2026
Merged

ThomasK33 merged 6 commits into
mainfrom
stable-api/3-terminal-router

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

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 TerminalSessionRouter for each client and passes null in the gap. Each TerminalView then subscribes again and calls term.clear(), and the reattach repeats the scrollback lines.

Implementation

  • TerminalRouterProvider creates one router per mount (and per popout mode). It calls router.setClient() with the live client (useLiveAPI()), so a reconnecting state that keeps the old client still reaches the router as offline. The router is never null after the first commit, so TerminalView keeps its one subscription.

  • router.setClient(client) keeps every session, subscriber and cached screen. For each live session it:

    1. aborts the old streams;
    2. bumps the session's generation;
    3. sends the last known size again, and waits for it (success or failure) before it starts attach, because the first attach message is the server's screen at the PTY size;
    4. restarts attach and onExit on 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 attach that 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 }, and TerminalView writes a VT full reset (ESC c) and the screen in one write. It does not call Terminal.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 no replace.

  • ConnectionLostError is not logged as a stream error, and sendInput no longer leaves an unhandled rejection when it fails.

  • To stay inside the CRAP gate, I split startStream into one method per stream loop, extracted writeScreenState() from TerminalView, 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.ts runs the real ghostty WASM: a replace leaves one copy of each line and keeps the terminal usable.
  • Hands-on run through a TCP proxy on c4c5f06 (7 hard drops in the sidebar, 1 in a popout, output during the gap, server restart): pass, with screenshots and a video in this comment. PR 6 runs the full hands-on check again.

Review record

  • Final head: 0aec776. Codex: no major issues on that head. CI: Required green. Review rounds: 6.
  • Independent advisor: ready with tracked follow-ups.
  • Follow-ups in PR 6 of this stack (each with a test):
    1. TerminalView's exit listener ignores ConnectionLostError. Needed once the provider keeps the last client while reconnecting.
    2. A resize that fails because the connection dropped is not replayed after the reconnect.
    3. A second view that subscribes during a reattach gets the reattach screen once, not twice.
  • Tracked separately: 🤖 Terminal tab repeats its scrollback when shown again #6330 (a hidden terminal tab repeats its scrollback when shown again, also on main).

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

@ThomasK33
ThomasK33 added this pull request to stack #6296 October 13, 2026 07:39
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T10:08:17.210835Z 0aec776 New commits
🔒 Security Review ✅ Completed 2026-10-13T10:10:04.089996Z 0aec776 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33 ThomasK33 changed the title refactor: the terminal router keeps its sessions and takes the new client after a reconnect 🤖 refactor: the terminal router keeps its sessions across a reconnect Oct 13, 2026

@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: 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".

Comment thread src/browser/terminal/TerminalRouterContext.tsx
Comment thread src/browser/terminal/TerminalSessionRouter.ts Outdated
Comment thread src/browser/components/TerminalView/TerminalView.tsx Outdated
@ThomasK33
ThomasK33 force-pushed the stable-api/3-terminal-router branch from 64b6aeb to c311093 Compare October 13, 2026 07:54
@ThomasK33
ThomasK33 force-pushed the stable-api/3-terminal-router branch from c4c5f06 to 976b0f4 Compare October 13, 2026 08:18
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

Dogfood: terminal across hard connection drops (head c4c5f06)

Result: pass. Setup: xum server behind a TCP proxy. The proxy was killed and restarted to drop the WebSocket without a close frame.

Check Result
seq 1 200 + marker, then 7 hard drops in the sidebar terminal Each of 1..200 and the marker once after every drop. No blank screen during the reattach.
Output made during the gap (sleep 8; echo gap-output) Shown once, on the right line, in 3 of 3 runs
Typing after the reconnect Works (echo after-N | tee aN wrote the files)
Pop-out terminal drop Pass
Console No memory access out of bounds, no other errors
Prompt colors, cursor row, tab title Unchanged

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):

After the reconnect on 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 xum • Model: anthropic:claude-opus-5-5 • Thinking: high

stable3-c4c5f062f3-reconnect.mp4

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 976b0f4d9a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 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".

Comment thread src/browser/terminal/TerminalSessionRouter.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 01a0dd8e4f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 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".

Comment thread src/browser/terminal/TerminalSessionRouter.ts
Comment thread src/browser/terminal/TerminalRouterContext.tsx
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 01a0dd8e4f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 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".

Comment thread src/browser/terminal/TerminalSessionRouter.ts
Base automatically changed from stable-api/2-harness to main October 13, 2026 10:05
Merged via the queue into main with commit 500cb28 Oct 13, 2026
61 of 64 checks passed
@ThomasK33
ThomasK33 deleted the stable-api/3-terminal-router branch October 13, 2026 10:41
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