Fix Windows backslash paths being mangled when adding an SSH target#14554
Merged
Conversation
Colengms
requested changes
Jun 29, 2026
Colengms
requested changes
Jun 30, 2026
Colengms
approved these changes
Jun 30, 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.
Found and fixed using Copilot with Claude Opus 4.8 in VS Code.
Problem
When adding an SSH target via "C/C++: Add SSH Target" (or anywhere
sshCommandToConfigruns), the user-entered SSH connection command is tokenized withshell-quote'sparse(). On Windows,parse()treats\as a POSIX escape character and strips it, so any backslash path in the command is corrupted:The resulting SSH config entry gets a broken
IdentityFile(and the same corruption applies to any other path-bearing argument, e.g. a config file or control path). The connection then fails because the key file path no longer exists.Root cause
shell-quote'sparse()follows POSIX shell rules, where\escapes the next character. Windows paths use\as the directory separator, soC:\Users\meis read as the escape sequences\U,\m, etc., and the backslashes are consumed.parse()is the wrong tool for splitting an SSH command into arguments: it treats\as an escape regardless of platform.Fix
Replace
shell-quote'sparse()with a small, self-contained tokenizer (splitArgsinsshCommandToConfig.ts) that behaves consistently on every platform: single and double quotes group their contents and are stripped, and unquoted whitespace separates arguments. Outside of quotes, a backslash escapes only a following whitespace character (so a Unix path like/home/me/my\ keykeeps its space), but before any other character it stays literal (so Windows paths likeC:\Users\me\keyare preserved). This keeps the cross-platform behavior the originalparse()-based code had, minus the bug where every backslash was treated as an escape.This replaces the entire
parse(...)call:The unused
shell-quoteandisWindowsimports were removed fromsshCommandToConfig.ts. This was the lastparse()consumer, and the only other consumer (quote()insettings.ts) was replaced with an equivalent string, soshell-quoteis removed entirely: dropped frompackage.json,yarn.lock, andThirdPartyNotices.txt.Alternatives considered
Doubling every
\before callingparse()over-escapes backslashes inside single-quoted segments (whichshell-quotepreserves literally), turning'C:\Users\me\key'intoC:\\Users\\me\\key. Reusing the sharedextractArgshelper does not work either: on Windows it implements cmd-style parsing where single quotes are literal, sossh -i 'C:\Users\me\key'would keep the surrounding quotes inIdentityFile(and split on spaces inside single-quoted paths). A dedicated tokenizer avoids both problems.Validation
Covered by new unit tests in
test/unit/sshCommandToConfig.test.ts(splitArgstokenization plus end-to-endsshCommandToConfigIdentityFileparsing):ssh -i C:\Users\me\key.pem user@host-> path preserved asC:\Users\me\key.pemssh -i "C:\Program Files\me\key.pem" user@host-> quoted path with a space preservedssh -i 'C:\Users\me\key' user@host-> single quotes stripped, path preservedssh -i /home/me/key\ with\ spaces user@host-> backslash-escaped spaces kept"C:\Program Files\" next-> quoted trailing backslash stays literal, not joined to the next arg\\server\share-> UNC path keeps both leading backslashesssh hello@microsoft.com -A-> unchanged (no paths)ssh -i /home/me/.ssh/id_rsa user@host-> unchanged (forward slashes)