Paste with Ctrl removes leading special character in SSH #21708

Closed
opened 2026-01-31 07:52:41 +00:00 by claunia · 13 comments
Owner

Originally created by @fbienz on GitHub (May 14, 2024).

Windows Terminal version

1.20.11271.0

Windows build number

10.0.22631.0

Other Software

openssh-client/jammy-security,now 1:8.9p1-3ubuntu0.6

Steps to reproduce

Connect to remote Linux server via SSH using WSL or Powershell.
Paste a string starting with one of these characters []{} using Ctrl+Shift+v, Ctrl+v or Ctrl+ Right Click.
Example: [rlkdjf[]{}dt

Expected Behavior

Complete String gets pasted into the Shell.
image

Actual Behavior

Leading character and the second character are removed from the String.
image

Using Shift+Insert or Right Click does not remove the leading characters.

This has worked until the latest update (winget package). I'm assuming this is form the 1.20 update.

Originally created by @fbienz on GitHub (May 14, 2024). ### Windows Terminal version 1.20.11271.0 ### Windows build number 10.0.22631.0 ### Other Software openssh-client/jammy-security,now 1:8.9p1-3ubuntu0.6 ### Steps to reproduce Connect to remote Linux server via SSH using WSL or Powershell. Paste a string starting with one of these characters `[]{}` using `Ctrl+Shift+v`, `Ctrl+v` or `Ctrl+ Right Click`. Example: `[rlkdjf[]{}dt` ### Expected Behavior Complete String gets pasted into the Shell. ![image](https://github.com/microsoft/terminal/assets/104909004/fce35d42-3c10-4b94-b270-d21cbe4fddb4) ### Actual Behavior Leading character and the second character are removed from the String. ![image](https://github.com/microsoft/terminal/assets/104909004/5abf88a1-d69c-495f-b4d8-80a872b26cc5) Using `Shift+Insert` or `Right Click` does not remove the leading characters. This has worked until the latest update (winget package). I'm assuming this is form the 1.20 update.
claunia added the Issue-BugArea-VTNeeds-Tag-FixProduct-TerminalPriority-1 labels 2026-01-31 07:52:42 +00:00
Author
Owner

@github-actions[bot] commented on GitHub (May 14, 2024):

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

@github-actions[bot] commented on GitHub (May 14, 2024): Hi I'm an AI powered bot that finds similar issues based off the issue title. Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you! ### Closed similar issues: - [Pasting text while in ssh session drops special characters (#8398)](https://github.com/microsoft/terminal/issues/8398), similarity score: 0.80 - [Escape characters surround pasted text inside ssh sessions (#14838)](https://github.com/microsoft/terminal/issues/14838), similarity score: 0.77 > Note: You can give me feedback by thumbs upping or thumbs downing this comment.
Author
Owner

@DHowett commented on GitHub (Aug 22, 2024):

Thanks for the report! I'm sorry that we haven't been able to reproduce this yet. I just gave it a try with 1.22 (Canary) in zsh and bash over ssh, one of which supports bracketed paste mode and the other of which does not.

Would you be able to test this out in the Canary channel/?

@DHowett commented on GitHub (Aug 22, 2024): Thanks for the report! I'm sorry that we haven't been able to reproduce this yet. I just gave it a try with 1.22 (Canary) in zsh and bash over ssh, one of which supports bracketed paste mode and the other of which does not. Would you be able to test this out in the Canary channel/?
Author
Owner

@fbienz commented on GitHub (Aug 23, 2024):

Hi,

I tested it again using following version:
Terminal (Portable)
Version: 1.22.240822001-llm

The Problem stays the same.

I have learned a few things though:

  • zsh does not have this issue in either version. (although i could only test it by launching zsh through bash)
  • The behavior is different for Debian 10 vs Debian 12 remote host

In case the remote host is Debian 12, pasting works without the brackets being removed, except when pasting a password in the password mask.
In that case only right Shift+Insert and Right Click paste correctly.

Since there were some updates, this is the current version I'm using: 1.20.11781.0

@fbienz commented on GitHub (Aug 23, 2024): Hi, I tested it again using following version: Terminal (Portable) Version: 1.22.240822001-llm The Problem stays the same. I have learned a few things though: - zsh does not have this issue in either version. (although i could only test it by launching zsh through bash) - The behavior is different for Debian 10 vs Debian 12 remote host In case the remote host is Debian 12, pasting works without the brackets being removed, except when pasting a password in the password mask. In that case only right `Shift+Insert` and `Right Click` paste correctly. Since there were some updates, this is the current version I'm using: 1.20.11781.0
Author
Owner

@carlos-zamora commented on GitHub (Jan 15, 2025):

Does this repro if you use ssh within WSL /?

@carlos-zamora commented on GitHub (Jan 15, 2025): Does this repro if you use ssh within WSL /?
Author
Owner

@fbienz commented on GitHub (Jan 20, 2025):

Does this repro if you use ssh within WSL /?

Yes? I'm not sure I understand your question. Is additional Information required?

@fbienz commented on GitHub (Jan 20, 2025): > Does this repro if you use ssh within WSL /? Yes? I'm not sure I understand your question. Is additional Information required?
Author
Owner

@carlos-zamora commented on GitHub (Jan 22, 2025):

Can you share the output of ssh -V on all client machines that are reproducing the issue /?

@carlos-zamora commented on GitHub (Jan 22, 2025): Can you share the output of `ssh -V` on all client machines that are reproducing the issue /?
Author
Owner

@fbienz commented on GitHub (Jan 27, 2025):

The clients have the following SSH version:
WSL: OpenSSH_8.9p1 Ubuntu-3ubuntu0.10, OpenSSL 3.0.2 15 Mar 2022
PowerShell: OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2

@fbienz commented on GitHub (Jan 27, 2025): The clients have the following SSH version: WSL: `OpenSSH_8.9p1 Ubuntu-3ubuntu0.10, OpenSSL 3.0.2 15 Mar 2022` PowerShell: `OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2`
Author
Owner

@j4james commented on GitHub (Apr 14, 2025):

I believe this bug is dependent on the keyboard layout. I can reproduce it when using a German or French layout, but not with a UK or US English layout.

And note that you need to start the session with the German or French layout already selected. If you open a tab with an English layout, and then switch to German, the bug won't be triggered. Also if you open a tab with the German layout, and then switch to US English, you should still be able to trigger the bug.

I'm guessing it might have something to do with the way we convert escape sequences into keyboard events before converting them back to escape sequences again (assuming we still do that). Possibly related to #17656 and #15083.

@j4james commented on GitHub (Apr 14, 2025): I believe this bug is dependent on the keyboard layout. I can reproduce it when using a German or French layout, but not with a UK or US English layout. And note that you need to start the session with the German or French layout already selected. If you open a tab with an English layout, and then switch to German, the bug won't be triggered. Also if you open a tab with the German layout, and then switch to US English, you should still be able to trigger the bug. I'm guessing it might have something to do with the way we convert escape sequences into keyboard events before converting them back to escape sequences again (assuming we still do that). Possibly related to #17656 and #15083.
Author
Owner

@fbienz commented on GitHub (Apr 22, 2025):

@j4james Changing the keyboard layout to US did solve this issue in a new Terminal.
For reference, I'm using the Swiss German layout as default.

@fbienz commented on GitHub (Apr 22, 2025): @j4james Changing the keyboard layout to US did solve this issue in a new Terminal. For reference, I'm using the Swiss German layout as default.
Author
Owner

@sylann commented on GitHub (Apr 30, 2025):

I can confirm too.

No problem if I start a new WSL shell session after selecting a US qwerty layout.
Also, it keeps working if I change the layout after that.

But the problem happens if I start a session with a French azerty layout or a custom one, and still happens after I change the layout to US.

@sylann commented on GitHub (Apr 30, 2025): I can confirm too. No problem if I start a new WSL shell session after selecting a US qwerty layout. Also, it keeps working if I change the layout after that. But the problem happens if I start a session with a French azerty layout or a custom one, and still happens after I change the layout to US.
Author
Owner

@j4james commented on GitHub (Jun 21, 2025):

In case anyone is curious what's actually happening here, this is the basic sequence of events:

  1. When the terminal pastes something, that content reaches conpty as a stream of characters. To keep things simple, let's assume in this case we're pasting a single [ character.
  2. In order to support legacy console apps that use the ReadConsoleInput API, those characters are then translated into press and release keyboard events.
  3. If you've got a German keyboard layout, the [ character is generated with AltGr+8, so that produces four events: AltGr press, 8 press, 8 release, AltGr release.
  4. Windows has this thing were it can emulate AltGr with Ctrl+Alt, so for consistency (I think) an AltGr event is always sent with the Ctrl flag set in the control key state, even though Ctrl isn't actually pressed.
  5. Because we're in WSL, these keyboard events now need to get translated back into a character stream, and in this process, control keys and release events are mostly ignored, so it's really just the 8 press event that we're translating.
  6. As mentioned in point 4, the 8 event has both Ctrl and AltGr flags set, so it looks like Ctrl+AltGr+8, but the code is actually already designed to handle that - it keeps track of when the control keys are pressed, and only considers the Ctrl key to be valid if there is a significant gap between it and the Alt key.
  7. But when you're pasting using a Ctrl+V shortcut, the input handler is going to receive a Ctrl press event before the paste is triggered. So by the time the auto-generated AltGr and 8 events arrive, there has been a significant gap since the Ctrl press, so it is considered valid.
  8. The end result is that the system sees this as a Ctrl+AltGr+8 key combination, which is converted into a ^[ character.

This would not be a problem if we could bypass that temporary conversion into keyboard events, and just pass the original input stream straight through to the WSL app that is expecting a character stream. That should also improve the paste performance. However, I'm not sure if that is actually feasible, because I don't know if we can tell in advance how an app is going to be reading the input.

Another potential solution would be to change the way we generate keyboard events for pasted content. If we can tell that the character stream is part of a paste (e.g. if we've previously been receiving win32-input sequences, and now we're receiving a simple character stream), then instead of generating press/release events for those characters, we could just convert them into VK_PACKET events, which should pass through the system cleanly (they're unaffected by the control key state).

@j4james commented on GitHub (Jun 21, 2025): In case anyone is curious what's actually happening here, this is the basic sequence of events: 1. When the terminal pastes something, that content reaches conpty as a stream of characters. To keep things simple, let's assume in this case we're pasting a single `[` character. 2. In order to support legacy console apps that use the [`ReadConsoleInput`](https://learn.microsoft.com/en-us/windows/console/readconsoleinput) API, those characters are then translated into press and release keyboard events. 3. If you've got a German keyboard layout, the `[` character is generated with <kbd>AltGr</kbd>+<kbd>8</kbd>, so that produces four events: <kbd>AltGr</kbd> press, <kbd>8</kbd> press, <kbd>8</kbd> release, <kbd>AltGr</kbd> release. 4. Windows has this thing were it can emulate <kbd>AltGr</kbd> with <kbd>Ctrl</kbd>+<kbd>Alt</kbd>, so for consistency (I think) an <kbd>AltGr</kbd> event is always sent with the <kbd>Ctrl</kbd> flag set in the control key state, even though <kbd>Ctrl</kbd> isn't actually pressed. 5. Because we're in WSL, these keyboard events now need to get translated back into a character stream, and in this process, control keys and release events are mostly ignored, so it's really just the <kbd>8</kbd> press event that we're translating. 6. As mentioned in point 4, the <kbd>8</kbd> event has both <kbd>Ctrl</kbd> and <kbd>AltGr</kbd> flags set, so it looks like <kbd>Ctrl</kbd>+<kbd>AltGr</kbd>+<kbd>8</kbd>, but the code is actually already designed to handle that - it keeps track of when the control keys are pressed, and only considers the <kbd>Ctrl</kbd> key to be valid if there is a significant gap between it and the <kbd>Alt</kbd> key. 7. But when you're pasting using a <kbd>Ctrl</kbd>+<kbd>V</kbd> shortcut, the input handler is going to receive a <kbd>Ctrl</kbd> press event before the paste is triggered. So by the time the auto-generated <kbd>AltGr</kbd> and <kbd>8</kbd> events arrive, there _has_ been a significant gap since the <kbd>Ctrl</kbd> press, so it _is_ considered valid. 8. The end result is that the system sees this as a <kbd>Ctrl</kbd>+<kbd>AltGr</kbd>+<kbd>8</kbd> key combination, which is converted into a `^[` character. This would not be a problem if we could bypass that temporary conversion into keyboard events, and just pass the original input stream straight through to the WSL app that is expecting a character stream. That should also improve the paste performance. However, I'm not sure if that is actually feasible, because I don't know if we can tell in advance how an app is going to be reading the input. Another potential solution would be to change the way we generate keyboard events for pasted content. If we can tell that the character stream is part of a paste (e.g. if we've previously been receiving win32-input sequences, and now we're receiving a simple character stream), then instead of generating press/release events for those characters, we could just convert them into `VK_PACKET` events, which should pass through the system cleanly (they're unaffected by the control key state).
Author
Owner

@lhecker commented on GitHub (Jun 23, 2025):

@j4james I believe the issue can be put differently:

We translate pastes to INPUT_RECORDs here:
00ee88400a/src/terminal/adapter/InteractDispatch.cpp (L61-L75)

...which then gets indirectly translated back to text here:
00ee88400a/src/host/inputBuffer.cpp (L706-L715)

...and then finally translated back to INPUT_RECORDs here:
00ee88400a/src/host/inputBuffer.cpp (L794-L811)

...and that doesn't round-trip. Calling InputBuffer::WriteString instead of InputBuffer::Write would solve this issue for Windows Terminal in particular. If you remember, we discussed this here: https://github.com/microsoft/terminal/pull/17833#discussion_r1739935654

Can we create a version of InteractDispatch::WriteString that doesn't go through TerminalInput? 🤔

@lhecker commented on GitHub (Jun 23, 2025): @j4james I believe the issue can be put differently: We translate pastes to `INPUT_RECORD`s here: https://github.com/microsoft/terminal/blob/00ee88400aab0cd94409204ce0c6d20854ba7eff/src/terminal/adapter/InteractDispatch.cpp#L61-L75 ...which then gets indirectly translated back to text here: https://github.com/microsoft/terminal/blob/00ee88400aab0cd94409204ce0c6d20854ba7eff/src/host/inputBuffer.cpp#L706-L715 ...and then finally translated back to `INPUT_RECORD`s here: https://github.com/microsoft/terminal/blob/00ee88400aab0cd94409204ce0c6d20854ba7eff/src/host/inputBuffer.cpp#L794-L811 ...and that doesn't round-trip. Calling `InputBuffer::WriteString` instead of `InputBuffer::Write` would solve this issue for Windows Terminal in particular. If you remember, we discussed this here: https://github.com/microsoft/terminal/pull/17833#discussion_r1739935654 Can we create a version of `InteractDispatch::WriteString` that doesn't go through `TerminalInput`? 🤔
Author
Owner

@j4james commented on GitHub (Jun 24, 2025):

Calling InputBuffer::WriteString instead of InputBuffer::Write would solve this issue for Windows Terminal in particular.

That's assuming Windows Terminal is always in win32 input mode, which isn't guaranteed. But looking at the code again now, I think we can possibly just call InputBuffer::WriteString when VT input mode is enabled, regardless of what mode the terminal is using.

Can we create a version of InteractDispatch::WriteString that doesn't go through TerminalInput? 🤔

Isn't that just InteractDispatch::WriteStringRaw? In which case a simple solution could just be an update to InputStateMachineEngine::ActionPrintString to look like this:

if (_pDispatch->IsVtInputEnabled())
{
    _pDispatch->WriteStringRaw(string);
}
else
{
    _pDispatch->WriteString(string);
}

But I think there is a flaw in this approach. The WriteString path (which goes through InputBuffer::Write), does some CONSOLE_SUSPENDED handling which would not occur on the WriteStringRaw path (which goes through InputBuffer::WriteString). So it becomes a little more complicated if console suspension is expected to work (I'm not actually sure about that).

And if we do need to support console suspension, it may be easier to handle everything in the InteractDispatch::WriteString method itself, and in the raw case it can call a variant of InputBuffer::WriteString that includes the CONSOLE_SUSPENDED checks.

@j4james commented on GitHub (Jun 24, 2025): > Calling `InputBuffer::WriteString` instead of `InputBuffer::Write` would solve this issue for Windows Terminal in particular. That's assuming Windows Terminal is always in win32 input mode, which isn't guaranteed. But looking at the code again now, I think we can possibly just call `InputBuffer::WriteString` when VT input mode is enabled, regardless of what mode the terminal is using. > Can we create a version of `InteractDispatch::WriteString` that doesn't go through `TerminalInput`? 🤔 Isn't that just `InteractDispatch::WriteStringRaw`? In which case a simple solution could just be an update to `InputStateMachineEngine::ActionPrintString` to look like this: ```cpp if (_pDispatch->IsVtInputEnabled()) { _pDispatch->WriteStringRaw(string); } else { _pDispatch->WriteString(string); } ``` But I think there is a flaw in this approach. The `WriteString` path (which goes through `InputBuffer::Write`), does some `CONSOLE_SUSPENDED` handling which would not occur on the `WriteStringRaw` path (which goes through `InputBuffer::WriteString`). So it becomes a little more complicated if console suspension is expected to work (I'm not actually sure about that). And if we _do_ need to support console suspension, it may be easier to handle everything in the `InteractDispatch::WriteString` method itself, and in the raw case it can call a variant of `InputBuffer::WriteString` that includes the `CONSOLE_SUSPENDED` checks.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#21708