terminal
terminal copied to clipboard
Terminal cannot be closed when the defterm client closes too quickly (and creates a window with no tabs)
[!IMPORTANT]
📌 Pinned comment: https://github.com/microsoft/terminal/issues/15936#issuecomment-1710747811 We're pretty sure this comes down to two effects:
- The client app closes before the Terminal can accept the handoff and create a tab for it.
- The Terminal can't be closed when it has no tabs open.
We should really fix both of these at the same time.
Windows Terminal version
1.17.12191.0
Windows build number
25941.1000
Other Software
No response
Steps to reproduce
There's no routine to easily reproduce it. It happens occasionally when I use some command line tools (like npm) that will invoke another terminal/cmd console. When the process is done, the invoked terminal will not close successfully (only the tab is closed, which results in an empty terminal with no tab), and you are unable to close it manually (because there's no tab).
Expected Behavior
A terminal losing any tabs should not exist.
Actual Behavior
There's no routine to easily reproduce it. It happens occasionally when I use some command line tools (like npm) that will invoke another terminal/cmd console.
I don't think anyone on the team has encountered this before. Are there some steps we could follow to attempt to reproduce it? (For instance if you list the tools and commands that cause the issue for you.)
I happened to hit this bug many times. The strange terminal window (without any tabs on it) is invoked with -Embedding arg in the background by something, not sure what but ~I've a feeling~ (I've lost the feeling 😄) that it's VS.
More Info:
It seems to be invoked by ~wininit.exe~ svchost.exe?
This is quite funny, but we have a repro right in our test suite 😂 (So I was correct, VS was related in this, maybe indirectly)
Step to reproduce:
- Set Windows Terminal as the "Default Terminal application".
- Open VS and run the test named "WindowsTerminal.UIA.Tests".
- You should see a blank WT window pop up in the background.
video demo: https://youtu.be/lNJOSjXioOY
My theory is that if the client application exits fast enough, we start a defterm window, but we never get it to the point of making an actual tab.
I see this sometimes (but usually only when I have a horribly busted defterm install).
I wonder if this repros after the process model changes in 1.18 EDIT: #16234 would imply that yes, this repros on 1.18
My repro case:
- Run
npm run devto start a server. - Force close it by pressing
ctrl+conce, and no need to confirm the ensued promptTerminate batch job (Y/N)?. - Normally, a terminal window pops up and closes itself quickly. Sometimes there's a second window following, but it will stay and it contains no tab.
I can also reproduce with:
- Set Windows Terminal as the "Default Terminal application".
- Add a pretty short running Task To Task Scheduler executing a
.cmdfile on logon. - At every Logon you are greeted by a terminal window with no body (see the background of the picture)
For me it looks even more odd, as the windows don't even have a fill:
@DanielHabenicht I suppose you're Win10, and the fill we can see in other repros is Mica which isn't available on Win10. Therefore, no fill? 🤔
Yes, I am on windows 10. You can see the version in the issue linked above.
Here is another repro. Steps inside: https://github.com/microsoft/terminal/issues/16281
I noticed that sometimes when this happens, in addition to Windows Terminal launching without a tab (the reported bug) it does also start the classic shell that then runs the actual PowerShell command/script.
Edit: By the way, this is the script I'm running.
Start-Sleep 1 # Try to work around https://github.com/microsoft/terminal/issues/15936
if((Test-Path "C:\code") -and $env:OneDriveCommercial -and (Test-Path $env:OneDriveCommercial)) {
# With monitoring
# robocopy "C:\code" (Join-Path $env:OneDriveCommercial code_backup) /MIR /TIMFIX /MON:1 /MOT:1 /DCOPY:DAT /XD venv .venv node_modules __pycache__ /NFL /NDL /R:10 /W:1
# Without monitoring
$p = Start-Process robocopy -ArgumentList "C:\code `"$(Join-Path $env:OneDriveCommercial code_backup)`" /MIR /TIMFIX /DCOPY:DAT /XD venv .venv node_modules __pycache__ /NFL /NDL /R:10 /W:1" -NoNewWindow -Wait -PassThru
Start-Sleep 1 # Try to work around https://github.com/microsoft/terminal/issues/15936
exit $p.ExitCode
} else {
Start-Sleep 1 # Try to work around https://github.com/microsoft/terminal/issues/15936
exit 1
}
Hi, are there any updates on this?
I'm investigating this currently. I could have swore I used to have a minimal repro for this, but I'm having a hard time currently 😕
If someone who's seeing this consistently could Capture a Debug ETL Trace and share it here, that would help immensely. I'm guessing there's one of two things going on:
- We're failing to initialize the intermediate OpenConsole, after starting the terminal for handoff.
- In this case, I'd expect to see the following on the
Microsoft.Windows.Console.HostproviderSrvInit_PrepareToCreateDelegationTerminalSrvInit_CreatedDelegationTerminal- but then not a
SrvInit_DelegateToTerminalSucceeded, nor aReceiveTerminalHandoff_Failed(this would be logged toMicrosoft.Windows.Terminal.Connection)
- In this case, I'd expect to see the following on the
- We're failing to accept the handoff from the Terminal.
- this is like the above, but we would actually log a
ReceiveTerminalHandoff_Failed
- this is like the above, but we would actually log a
If we for whatever reason see a ReceiveTerminalHandoff_Success, then that would be surprising to me.
notes
First things first:
- Conhost starts up. It finds that it's got a delegation console, and it starts that. (we can assume this works, because a terminal does get started)
- OpenConsole starts up, as the target of a defcon handoff.
ConsoleEstablishHandoff:- It finds the target terminal to also delegate to.
- Does quite a bit of pipe manipulation, to get ready to call the terminal. (we can assume this works, because a terminal does get started)
- We open a handle to the target client process. We hang onto that handle until after the end of
ConsoleEstablishHandoff. - Calls
CoCreateInstance(g.delegationPair.terminal,.... (this instantiates terminal, below.) - gets our connection info: Icon, lnk, settings.
- ⚠️ yikes there's a
RETURN_IF_NTSTATUS_FAILED(ConsoleInitializeConnectInfo(...))in here. If that does fail, we've started terminal, but failed to do a handoff.
- ⚠️ yikes there's a
- We call
handoff->EstablishPtyHandoff- This will get handled in
CTerminalHandoff::EstablishPtyHandoff, which is broken down below.
- This will get handled in
now, over in the terminal, our registered default terminal handoff target. The following occurs when the terminal is launched as a COM server
These paths to _StartInboundListener take care of both glomming and non glomming.
- In the glomming scenario, the monarch tells the MRU window to
ExecuteCommandline, and that will start the inbound listener - in the non-glomming,
TerminalWindow::Initializesets the inbound listener.
In both cases, the inbound listener is started immediately if the page already laid out, or we set a flag and wait till the first layout to accept the handoff.
TerminalWindow::Initialize, if_appArgs.IsHandoffListenerORTerminalWindow::ExecuteCommandline, again, if_appArgs.IsHandoffListenerTerminalPage::SetInboundListener- sets
_shouldStartInboundListener=true - If we were already laid out the first time, immediately calls
_StartInboundListener
- sets
TerminalPage::_OnFirstLayoutcalled when the page first lays out- process startup actions
- call
_StartInboundListenerto possibly start accepting handoff
So here, we're already laid out, and we know this window wants to accept the handoff.
TerminalPage::_StartInboundListenerif_shouldStartInboundListener...- registers the
ConptyConnection::NewConnectioncallback - calls
ConptyConnection::StartInboundListenerCTerminalHandoff::s_StartListeningpasses theConptyConnection::NewHandofffunction as the callback to:CoRegisterClassObject(CTerminalHandoff, us, as a local server, single-use)
- returns
- registers the
later, when conpty is ready....
CTerminalHandoff::EstablishPtyHandoff- THROWS if it fails, for ANY reason
- ⚠️ Throwing would also just never call the callback, nor signal an error.
- STOPS listening
- duplicates handles
- calls it's registered callback
ConptyConnection::NewHandoffraises the handoff, which is handled inTerminalPage::_OnNewConnectionaccepts the connection from conpty- possibly
co_awaits to get onto the window thread. - Creates the tab
_CreateNewTabFromPane(MakePane(..., existingConnection))
- possibly
- THROWS if it fails, for ANY reason