Conversation
`loadURL`/`loadFile` resolve on `did-finish-load`, so the listener that handed the renderer its ptyHost MessagePort was registered ~320 lines after the event it waited for had already fired. `attachWindow` never ran, and `electronAPI.ptyHost.onData` had no source. That is fatal because TerminalPanel permanently short-circuits the legacy `terminal:output` IPC handler as soon as a `ptyId` arrives, so the port is the only byte source for a ptyHost-spawned panel. Every new terminal painted its first frame and then went silent while main's emulator stayed correct: typing reached the shell but never echoed, and manual Refresh repainted once without restoring live updates. Windows only, because `usePtyHost` defaults to on for win32. - register the hand-off before the awaited load, and use `on` so every load re-attaches - make `attachWindow` replace a stale port pair instead of returning early; `webContents.id` survives a reload but the preload closure holding the port does not - warn once in preload when a port is missing instead of no-opping silently, which is what hid this
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.
Description
On Windows, every newly spawned terminal painted its first frame and then went
silent. Typing reached the shell but never echoed, and the manual Refresh button
repainted once without restoring live updates.
The cause is not rendering.
createWindow()handed the renderer its ptyHostMessagePortfrom adid-finish-loadlistener registered after the awaitedpage load:
loadURL/loadFileresolve ondid-finish-load, so the listener was armedafter the event it waited for had already fired.
attachWindownever ran andelectronAPI.ptyHost.onDatahad no source.That is fatal because
TerminalPanel.tsxpermanently short-circuits the legacyterminal:outputIPC handler as soon as aptyIdarrives, making the port theonly byte source for a ptyHost-spawned panel. Main's headless emulator stayed
correct throughout, which is exactly why
terminal:getState(Refresh) stillpainted a single frame and the terminal looked frozen rather than broken.
Windows only because
configManager.tssetsusePtyHost: process.platform === 'win32'.macOS/Linux never fork the supervisor and stay on the working IPC path.
Changes
main/src/index.ts— register the port hand-off before the awaited load, anduse
onrather thanonceso every load re-attaches.main/src/ptyHost/ptyHostSupervisor.ts—attachWindownow replaces a staleport pair instead of returning early.
webContents.idsurvives a reload but thepreload closure holding the renderer end does not, so the old guard would leave a
reloaded window permanently without a data source. The destroy-cleanup listener is
registered only on first attach so re-attaches don't accumulate listeners.
main/src/preload.ts— warn once when a ptyHost frame is posted with no port.The previous silent no-op is what hid this bug.
attachWindownow takes a narrow structuralPtyHostPortTargetinstead of thefull
WebContents, so it is testable without standing up a real renderer.Type of Change
Checklist
pnpm typecheckandpnpm lintlocallyCritical Areas Modified
Additional Notes
Evidence the listener never ran
[ptyHost] attached window webContentsId=appears nowhere across two full daysof production logs, while
[ptyHost] UtilityProcess exited (code=0)does — provingthe supervisor was running and only the attach was missing. After the fix,
[ptyHost] attached window webContentsId=1appears on a live dev run.End-to-end verification
Drove a real terminal through the RunPane CLI against a dev instance on an isolated
PANE_DIRand a throwaway repo: ~500KB streamed in 897ms with no flow-controlstall. Acks are only emitted from inside xterm's post-render callback, so that
round trip proves
main → port → preload → xterm → ack → main. With a dead portptyHost.ackis a silent no-op, so the PTY would have paused at the 100KBHIGH_WATERMARKand crawled on 5sPAUSE_SAFETY_TIMEOUTcycles.Note on the "tested the Electron app locally" checkbox: verification was done via a
PANE_DIR=~/.pane_test pnpm devinstance driven by the RunPane CLI rather than byhand through
pnpm electron-dev.Tests
main/src/ptyHost/ptyHostSupervisor.attachWindow.test.tsadds 4 tests covering theport hand-off and the re-attach contract. Two of them were confirmed to fail against
the previous early-return guard, so they are not vacuous.
main/src/test/setup.tsgains a
MessageChannelMain/MessagePortMainstand-in.Main suite: 22 files / 47 tests fail identically before and after this change —
the pre-existing
better-sqlite3-multiple-ciphersABI mismatch documented inAGENTS.md, all SQLite-dependent. The 4 new passing tests are the only delta.