fix: give a new PTY the terminal's real size, not the 80x24 spawn default - #552
Merged
Merged
Conversation
…ault A terminal the user never happened to resize by hand kept its PTY at 80x24 for its entire life. The on-screen grid looked right, but the shell wrapped at 80 columns and any TUI started in it drew its frame to 80 — a restored Claude Code session rendered its welcome banner into the left ~75% of the panel with dead space beside it. Nudging the panel border by a pixel fixed it permanently, which is what made the bug look cosmetic. getOrCreate registers the registry entry BEFORE awaiting the spawn, so that concurrent callers share one object. That also means attach() can — and in practice does — fit the xterm to its real container while the spawn is still in flight. Those fits call terminal.resize(), which fires onResize before wireTerminalListeners has attached the listener that forwards it to the PTY, so the size is applied to xterm and dropped for the PTY. Nothing corrects it afterwards, because fit() only resizes when the size CHANGES: the grid already matches the container, so every later fit is a no-op and the PTY never hears about it. Instrumenting both ends made the ordering unambiguous — six resizes logged against an unwired terminal, then the listeners attaching to a terminal already at 107x27, and zero resize IPCs for the whole restore. Push the terminal's current size once, right after the listeners are wired. The guard keeps it silent when the terminal really is still at the spawn size, so no pointless SIGWINCH reaches an already-correct PTY. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WsMgXwPNJJ4SgXgRff5wJk
Contributor
|
Looks good. The resize is sent after the listener is wired, and the guard avoids an unnecessary SIGWINCH when 80x24 is still correct. The regression coverage is solid. |
Anton-Horn
approved these changes
Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
The on-screen grid fits the panel, but the PTY stays at 80 columns. The shell wraps at 80, and any TUI started in that terminal draws its frame to 80.
Found via a restored Claude Code session rendering its welcome banner into the left ~75% of the panel with dead space beside it.
That "nudge fixes it forever" behaviour is what made it look cosmetic.
Cause
getOrCreateregisters the entry before awaiting the spawn, so that concurrent callers share one object:During that wait
attach()fits the xterm to its real container. Theterminal.resize()those fits perform firesonResizebefore the listener that forwards it to the PTY exists, so the size lands on xterm and is dropped for the PTY.Nothing corrects it afterwards, because
fit()only resizes on a change. The grid already matches the container, so every later fit is a no-op and the PTY is never told. Nudging the panel is the one thing that makes the size change again.Instrumenting both ends made the ordering unambiguous:
Three layers swallow the failure silently —
runtimeForTerminal(id)?.,RemoteRuntime's.catch(() => {}), and the daemon'sptys.get(id)?.— so nothing was logged anywhere. That made it look like a main-process or runtime problem when in fact the renderer never made the IPC call at all.Fix
Push the terminal's current size once, right after the listeners are wired:
The guard keeps it silent when the terminal genuinely is still at the spawn size (panel not laid out yet), so no pointless SIGWINCH reaches an already-correct PTY.
Tests
Two regression tests, and I checked they actually fail without the fix:
Typecheck passes; 232 terminal/panel tests pass.
Verification
Instrumented a dev build on the session-restore path:
main resizeTerminalwent from 0 calls to carrying the real sizes (107x27, 89x24) through to the PTY, andtput colschanged from 80 to the panel's actual width.Other entry paths — new window, detach, dock switch — were not exercised. The fix sits on the common path every fresh spawn takes, so they should be covered by construction, but the empirical check is one path only.
Relationship to #551
Independent, and they do not conflict. Both touch
terminalRegistry.test.tsbut in different regions;git merge-treeconfirms a clean automatic merge. Either can land first.The causes are unrelated too — #551 is about render scaling under canvas zoom, this one is about PTY size at spawn time.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WsMgXwPNJJ4SgXgRff5wJk