Skip to content

Parent the ConPTY console window to the VS Code window - #342784

Merged
Anthony Kim (anthonykim1) merged 9 commits into
mainfrom
anthonykim1/conpty-parent-window
Oct 23, 2026
Merged

Anthony Kim (anthonykim1) merged 9 commits into
mainfrom
anthonykim1/conpty-parent-window

Conversation

@anthonykim1

@anthonykim1 Anthony Kim (anthonykim1) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor
  • Send the HWND of the window hosting each local terminal to node-pty, which makes it the owner of the terminal's console window. Dialogs that console apps parent to their console window or its owner, like the Connect-AzAccount sign-in picker, then open in front of VS Code instead of behind it. Modal ones disable VS Code while they're open, as in Windows Terminal.
  • Start the handle lookup when the terminal is created, and send the handle before start() for new, revived, and reattached processes, using the window the terminal is in by then. Send it again when attachToElement() moves the terminal to another window.
  • Read the handle with IHostService.getNativeWindowHandle and send its bytes to the pty host unchanged as a VSBuffer, since node-pty takes the Buffer that Electron's getNativeWindowHandle() returns. Skip remote terminals, and never fail terminal startup if the lookup, the IPC, or the node-pty call fails.
  • Add setParentWindow along the clearBuffer IPC path. The pty host calls node-pty's setParentWindow right after spawn(), before node-pty launches the shell, or right away if the pty is already running, and trace-logs whether it was sent. Only on Windows with windowsUseConptyDll.
  • Keep the handle out of ITerminalProcessOptions, which are persisted for revive and would replay a stale HWND.
  • Add terminal.integrated.windowsReparentConsoleWindow (default on, ExP-controllable) to turn this off without turning off conpty.dll.
  • Add TerminalProcessManager tests for sending the handle before start(), window moves, startup races, passing the handle bytes through unchanged, and the setting.
  • If a dialog's process dies while the dialog is modal to VS Code, VS Code stays disabled. That's tracked in Re-enable the parent window when a modal dialog's process dies node-pty#996.
  • Bump node-pty to 1.2.0-beta.17, which adds setParentWindow from Add ConptyReparentPseudoConsole support node-pty#995.

Inspirations from:

Resolves https://github.com/microsoft/vscode-internalbacklog/issues/10198

Pass the native handle of the window hosting each terminal down to the pty
host and node-pty, which calls ConptyReparentPseudoConsole. Windows that
console apps parent to GetConsoleWindow(), such as the WAM sign-in picker
used by Connect-AzAccount, then open above VS Code instead of behind it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The required node-pty dependency update is missing, and overlapping window lookups can let startup proceed before parenting.

2 open issues
What changed in this PR

Adds Windows console ownership to local terminals so console-app dialogs appear above their hosting VS Code window.

Changes:

  • Threads native window handles through the terminal process and PTY services.
  • Sets ownership before startup and updates it when terminals move between windows.
  • Adds process-manager tests and updates the agent-host PTY mock.
File Description
src/​vs/​workbench/​contrib/​terminal/​test/​browser/​terminalProcessManager.test.ts Tests startup ordering and window changes.
src/​vs/​workbench/​contrib/​terminal/​electron-browser/​localPty.ts Forwards parent handles over IPC.
src/​vs/​workbench/​contrib/​terminal/​common/​terminal.ts Adds the process-manager parenting contract.
src/​vs/​workbench/​contrib/​terminal/​browser/​terminalProcessManager.ts Resolves and forwards native handles.
src/​vs/​workbench/​contrib/​terminal/​browser/​terminalInstance.ts Requests parenting during startup and attachment.
src/​vs/​platform/​terminal/​node/​terminalProcess.ts Applies ownership through node-pty.
src/​vs/​platform/​terminal/​node/​ptyService.ts Routes parenting to persistent processes.
src/​vs/​platform/​terminal/​node/​ptyHostService.ts Proxies parenting requests.
src/​vs/​platform/​terminal/​common/​terminal.ts Adds PTY parenting interfaces.
src/​vs/​platform/​agentHost/​test/​node/​agentHostTerminalManager.test.ts Updates the PTY test implementation.

Review effort: Balanced

@anthonykim1 Anthony Kim (anthonykim1) left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No correctness problem found in the plumbing: setParentWindow is sent ahead of start() for new, revived and reattached processes, remoteAuthority is already set in the manager constructor so the early return is right, and the stale-window check in setParentWindow holds for A→B→A moves. Inline comments are design, all p2:

  • _parentWindow duplicates the per-window memoization NativeHostService.getNativeWindowHandle already has.
  • readUInt32LE zero-extends the HWND; Windows' interop rule is sign-extension, which readInt32LE plus node-pty's Int64Value path gives for free.
  • The await in TerminalInstance serializes the handle lookup ahead of _createProcess; createProcess already has the right spot to await it.

Out of scope here but worth tracking: AgentHostPty and RemotePty don't implement setParentWindow, so agent-host terminals on Windows keep an unowned console window.

Blocked on microsoft/node-pty#995 shipping either way.

@anthonykim1 Anthony Kim (anthonykim1) left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three more p2s inline:

  • Dialogs that are modal to the console window's root owner now disable the VS Code window, and nothing re-enables it if the console process dies first.
  • windowsUseConptyDll: false is the only off switch.
  • setParentWindow isn't trace-logged like the other node-pty calls.

- Start the window handle lookup without awaiting it in TerminalInstance;
  createProcess now waits for the handle of the latest window before
  start(), so a move during startup can't let the shell start unowned.
- Drop the second per-window cache; NativeHostService already memoizes the
  lookup.
- Sign-extend the low 32 bits of the HWND, like Windows does for handles.
- Add terminal.integrated.windowsReparentConsoleWindow (experimental,
  ExP-controllable) as an off switch that keeps conpty.dll.
- Trace-log node-pty's setParentWindow and whether it applied.
- Drop the agent host TestPty stub now that IPty.setParentWindow is optional.
@anthonykim1

Anthony Kim (anthonykim1) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Pushed ae6b450 for the review; node-pty is at 909b13e (microsoft/node-pty#995).

Verified on Windows 11. The unit tests ran on my local checkout, which has the same production changes:

  • Type check against the patched node-pty typings: 0 errors. ESLint and hygiene are clean on the changed files.
  • Terminal unit tests (Electron): 4454 passing vs 4449 on the unpatched baseline, which is the 5 new tests; 0 failing. Node: 122 passing, same as the baseline.

Then in a dev build with both branches:

  • Connect-AzAccount in an existing shell: the WAM picker opens in front of VS Code, centered on it.
  • A modal dialog owned by the console window's root owner disables VS Code while it's open. Re-enabling VS Code when that dialog's process dies is now tracked in Re-enable the parent window when a modal dialog's process dies node-pty#996.
  • Move Terminal into New Window: the owner follows the terminal to the new window.
  • Reload Window: reconnected terminals are owned by the current windows, including a recreated auxiliary window with a new HWND, so no stale handles.
  • terminal.integrated.windowsReparentConsoleWindow: false: new terminals stay unowned, and turning it back on works.

On agent host terminals (from the first review): agreed, that's out of scope here. They spawn with in-box ConPTY, so they'd need conpty.dll before they can reparent. I'll track that separately.

The setting stays ExP-controllable through `experiment`, but it is not an
experimental setting.
node-pty only re-enables the parent window on exit when asked to
(conptyReenableParentWindowOnExit). VS Code only disables its windows
while showing a window they own, so it opts in. Also log setParentWindow's
result as "sent", since ConPTY applies the new owner asynchronously.
@anthonykim1

Anthony Kim (anthonykim1) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Pushed a274011. microsoft/node-pty#995 now only adds setParentWindow(), so TerminalProcess calls it right after spawn() instead of passing the handle in the spawn options, and no longer opts in to a re-enable. Re-enabling VS Code when a modal dialog's process dies is tracked in microsoft/node-pty#996.

Then e4ae49e: node-pty now takes the handle as the Buffer that Electron's getNativeWindowHandle() returns, so the VSBuffer from IHostService.getNativeWindowHandle goes to the pty host unchanged, with no conversion to a number. Since b2e2f5c, a failure in the pty host is only logged, so it can't break the launch.

Re-verified in a dev build with node-pty f699175:

  • New terminals are owned by VS Code, and the real Connect-AzAccount picker opens in front of it. VS Code is disabled while the picker is open and enabled again as soon as it's cancelled.
  • Moving a terminal into a new window moves the owner with it, and after a reload every shell is owned by its current window.
  • With terminal.integrated.windowsReparentConsoleWindow off, new terminals stay unowned; turning it back on works.

Electron unit tests: 4454 passing, including the 5 parent window tests; node tests: 122. Type check, ESLint, and hygiene are clean.

microsoft/node-pty#995 no longer has conptyParentWindow or
conptyReenableParentWindowOnExit, so TerminalProcess calls
setParentWindow right after spawn, which is before node-pty launches
the shell, and no longer opts in to the re-enable.
node-pty now rejects negative window handles, since HWNDs are pointers.
Sign-extend the signed 32-bit handle to an unsigned 64-bit value, the
same bits node-pty used before, and never let setting the parent window
fail the terminal launch.
node-pty now takes the HWND as the Buffer that Electron's
getNativeWindowHandle() returns, so send the VSBuffer from
IHostService.getNativeWindowHandle to the pty host unchanged instead of
converting it to a number and back.
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Base: 2bc7d5ab Current: a53c6042

No screenshot changes.

Adds IPty.setParentWindow() from microsoft/node-pty#995, which this PR uses.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review October 23, 2026 06:26
@anthonykim1 Anthony Kim (anthonykim1) added this to the 1.144.0 milestone Oct 23, 2026
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.

3 participants