fix(web, mobile): a failed terminal resize is sent again#32047
Problem
When a terminal.resize request fails while the WebSocket stays up, the client never sends the size again. The terminal surface only reports a grid when its measured size changes, and the resize result was dropped (reportFailure: false, result ignored). The PTY then keeps the old width indefinitely: stty size disagrees with the drawn pane, full-width output wraps, and spinners staircase, until the next drag to a new width. That matches the report: stty size printed 51 110 while the panel showed about 86 columns, and dragging again fixed it.
Reproduced on main (6577c6b618), web dev server, right-panel bash terminal, a 100 ms spinner, divider dragged mid-run. With every resize succeeding the PTY follows the pane (41 68 → 41 57). With exactly one resize request rejected by the server while the socket stays connected, the pane draws 44–46 columns but stty size stays at the previous width (53/60 columns) and the full-width line wraps, still stale more than a minute later with nothing touched.
This closes the failed-request path. What made the request fail on the reporter's macOS desktop is not confirmed (a node-pty resize throw surfaces as TerminalResizeError); the reproduction injects the failure.
Change
After a resize request fails, and the failure is not an interruption, the client resends the terminal's current grid once:
- Web and desktop,
ThreadTerminalDrawer: on failure the drawer records the writable session generation and calls the surface'sresendSize(), so the retry carries the current measured grid through the existing 150 ms debounce (resize commands are a latest-wins lane, so the failed size is never replayed over a newer one). The next report consumes the record; a retry's failure is not retried, and a record never carries over to another session generation. - Mobile,
createTerminalSessioninpackages/client-runtime:sendResize(retry)calls itself once withretry = trueafter a non-interrupt failure, re-checking the attachment generation and operate access. No shared state survives a detach or reconnect.
Failures stay silent. A request that keeps failing is sent at most twice; the existing per-generation replay on reconnect still applies. No server, contract or settings change.
Scope and approval
Fixes pingdotgg/t3code#31013
Triaged via the automated issue flow; reproduced on main with one injected resize failure.
Verification
Focused tests, each failing on main first:
apps/web/src/components/ThreadTerminalDrawer.browser.test.tsx:resends the terminal size after a resize request fails(main: only one 57×41 resize reached the command) andresends a failed resize once in each writable session(a retry suppressed after the session stopped being writable, and the bound on persistent failure). 4/4 pass.packages/client-runtime/src/state/terminal.test.ts:sends each failed resize again once, in every attachment generation(a failure after detach is not resent, generation 2's failure is resent once, its retry's failure is not, a new grid gets its own retry). 13/13 pass.- A mutation check removing the retry bound makes both suites fail (unbounded resend).
vp run check:changed: format, lint, knip, typecheck, tests and React compiler pass.
Real-app recordings, web dev server in Chrome 156 on Linux, 1440×900, DPR 2, dark theme, fresh database per side, same driver on both sides. Each recording first shows a successful drag (68 → 53 columns), then one drag narrower whose resize frame is rewritten in the page to cols: 0 so the server rejects it ("Expected a value greater than or equal to 1"), with the socket open throughout:
| Before 6577c6b618 | After db28804cc9 | |
|---|---|---|
| Rejected 44×46 resize | failure, no further resize sent | failure, then a valid 44×46 resize 184 ms later, success |
stty size after idle (+60 s) | 46 53 in a 44-column pane; full-width line wraps | 46 44; full-width line ends at the pane edge |
The head adds only f6525a2d4c after the captured build, which swaps in the equivalent isAtomCommandInterrupted helper and edits tests. An independent rebuild at the head reproduced the recovery (retry 178 ms after the rejected send, PTY back at 44 columns). The recordings (as GIFs) are in a PR comment; the final before/after stills and a zero-threshold diff are in the reply to the CodeRabbit summary. The differences between the final stills are the probe output and wrapping, the completed spinner glyphs and the sidebar thread age.
Not verified: the reporter's natural failure on the macOS desktop app; mobile on a device (covered by the client-runtime test; no mobile rendering changed); the server path where resizeLocked ignores a resize from an unregistered viewer while another viewer has a size, which returns success and is not covered by this retry.
AI-assisted; verified with the tests and tools listed above.
Checks
Added 7 commits
- fix(mobile): a failed terminal resize is sent again6ada42e
- fix(web): a failed terminal resize is sent againb466929
- fix(web): a failed terminal resize is retried in every sessiondb28804
- test(web): failed terminal resize is resent (reproduces #31013)de3b36e
- fix(mobile): a failed terminal resize is retried in every attachment generatione5a44fd
- test(client-runtime): failed terminal resize is resent (reproduces #31013)eb78c4a
- refactor(web): reuse the interrupt helper for the resize retryf6525a2
Approvability
Verdict: Approved at f6525a2
Macroscope's review found this PR approvable — Failed terminal resizes are retried once using the current dimensions, while retaining session and write-access checks. The change is limited to resize recovery and presents no obvious material risk requiring human judgment.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
-
The client runtime retries eligible failed terminal resize commands once. The terminal drawer also resends its measured size after a resize failure for a writable session generation. Tests cover retry behavior, reconnects, and changed grid sizes.
Changes: Terminal resize retry flow
Layer / File(s) Summary Client runtime resize retry
packages/client-runtime/src/state/terminal.ts,packages/client-runtime/src/state/terminal.test.tsThe runtime retries a failed resize command once unless the failure is an interruption. The retry uses the current grid size. Tests cover failures across attachment generations and after the grid size changes. Drawer resend by writable generation
apps/web/src/components/ThreadTerminalDrawer.tsx,apps/web/src/components/ThreadTerminalDrawer.browser.test.tsxAfter an eligible resize failure, the drawer resends the terminal surface’s measured size. It tracks the retry by writable session generation. Browser tests check that the request retains its parameters and that retries occur after a writable session reconnects. Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix · Severity of issue fixed: Low
Possibly related PRs
- pingdotgg/t3code#25458: Updates drawer resizing when the writable session generation changes; this PR adds failure retries to that resize path.
- pingdotgg/t3code#24352: Changes how measured terminal sizes reach the server and how viewer resize requests are applied; this PR retries failures in that path.
Suggested labels:
bugfixSuggested reviewers:
saphid
Merge Risk: External image Minimal · up to f6525
-
No actionable issue remains from this review; the change is mergeable after normal checks.
Pre-merge checks: Passed 3 · Inconclusive 1
-
Failed Failed checks: 1 inconclusive
Check name Status Explanation Resolution Linked Issues check Inconclusive [ #31013] The drawer retries a failed resize with the current grid. Focused tests cover bounded retries and session changes. The reported injected-failure test shows the PTY width matches the pane afte…Provide evidence of the original failure mechanism, or evidence that the affected resize path now keeps the PTY width aligned with the pane. Passed Passed checks: 3 passed
-
Check name Status Explanation Title check Passed The title clearly describes the failed terminal resize retry. Out of Scope Changes check Passed [ #31013] The web retry, mobile runtime retry, and their tests address stale terminal dimensions after resize failures. The mobile change supports the same terminal behavior on another client.Description check Passed The description covers the problem, changes, scope, and verification. It identifies unverified cases. The screenshots and recordings are referenced but not linked directly, and the AI model and harnes… Full details: Description check
-
Explanation
The description covers the problem, changes, scope, and verification. It identifies unverified cases. The screenshots and recordings are referenced but not linked directly, and the AI model and harness are not named.
-
Full details: Linked Issues check
-
Explanation
[
#31013] The drawer retries a failed resize with the current grid. Focused tests cover bounded retries and session changes. The reported injected-failure test shows the PTY width matches the pane after a retry. The cause of the original macOS failure is unknown. The retry does not cover a resize that the server ignores while returning success, so the evidence does not establish that the reported failure is fixed.Resolution
Provide evidence of the original failure mechanism, or evidence that the affected resize path now keeps the PTY width aligned with the pane.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info
-
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-31013
- File: .coderabbit.config.ts
- Commit: f6525a2
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
369e30ce-0fdf-42bd-acd3-06ef81f7daa6
External image Commits
-
Reviewing files that changed from the base of the PR and between 6577c6b6185167c0e502fa01f9e8e8f2dee6ff25 and f6525a2d4c05a731fc1b1715fb672bee6683f92b.
External image Files selected for processing: 4
-
apps/web/src/components/ThreadTerminalDrawer.browser.test.tsxapps/web/src/components/ThreadTerminalDrawer.tsxpackages/client-runtime/src/state/terminal.test.tspackages/client-runtime/src/state/terminal.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
External image Recent review info
-
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-31013
- File: .coderabbit.config.ts
- Commit: f6525a2
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
369e30ce-0fdf-42bd-acd3-06ef81f7daa6
External image Commits
-
Reviewing files that changed from the base of the PR and between 6577c6b6185167c0e502fa01f9e8e8f2dee6ff25 and f6525a2d4c05a731fc1b1715fb672bee6683f92b.
External image Files selected for processing: 4
-
apps/web/src/components/ThreadTerminalDrawer.browser.test.tsxapps/web/src/components/ThreadTerminalDrawer.tsxpackages/client-runtime/src/state/terminal.test.tspackages/client-runtime/src/state/terminal.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
-
On the inconclusive linked-issue check: this PR closes the path where a terminal.resize request fails while the connection stays up, which leaves the PTY at the old width until the next drag to a new width. That is the symptom in #31013. On main (6577c6b618), a single rejected resize in the web app kept stty size at 53 columns in a 44-column pane. With this branch, the client sends the current 44×46 grid once more about 150 ms after the failure, and stty size matches the pane.
What failed on the reporter's macOS desktop is not confirmed, as the PR body says. The server path that ignores a resize while returning success only applies when another viewer holds a size. The reporter had a single client, so that path is out of scope here.
Recordings for the Verification section: web dev server, Chrome 156 on Linux, 1440×900, DPR 2, dark theme, a fresh database per side, the same driver on both sides. Shown here as 4 fps GIFs scaled to 960 px.
Steps, the same on both sides:
- A 100 ms spinner runs while the divider is dragged once (68 → 53 columns). The resize succeeds.
- One outgoing
terminal.resizeframe is rewritten tocols: 0, so the server rejects it while the socket stays open. The spinner runs again and the divider is dragged narrower once (44 columns). Nothing is dragged or reconnected after that. - The width probe (
stty sizeand a line as wide as the PTY reports) runs again after about 40 s idle.
| Before 6577c6b618 | After db28804cc9 | |
|---|---|---|
| Rejected 44×46 resize | +29.9 s, no resize follows | +25.6 s, valid 44×46 resize at +25.8 s (184 ms later), success |
| Probe after idle | +78 s: 46 53, the line wraps | +63 s: 46 44, the line ends at the edge |
The only production change between db28804cc9 and the head (f6525a2d4c) swaps in the equivalent isAtomCommandInterrupted helper.