You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Browser opening could be faster on Windows by avoiding wslview #10466
After noticing that wslview felt really slow, I looked into the source code to see what the cause was. I noticed that wslview was doing a lot of stuff which, IMO, is unnecessary for the purposes of gh.
What wslview does is:
Queries the Windows Registry via reg.exe to check if the build number is high enough (not used when opening URLs)
Validates the URL by curling it and asserting that content was returned (a potential source of slowness, this can be skipped with --skip-validation-check on newer versions of wslview).
And, after these validations, what it boils down to is calling either /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "explorer.exe $URL" or /mnt/c/Windows/System32/cmd.exe /c explorer.exe "$URL". Starting a PowerShell process seems to contribute a lot to the slowness.
There's a bit of extra work in case a different prefix is used than /mnt or Windows is installed to a different drive than C:, but this should sum up the vast majority of cases.
I think these validations aren't necessary as long as LookPath is used, and explorer.exe can be called directly. I've noticed a pretty decent gain in speed when using GH_BROWSER=explorer.exe over GH_BROWSER=wslview.
Given I am on Windows When I invoke a command that opens the browser Thenexplorer.exe is used if it is on the PATH
Notes
It may not be possible to validate the A/C in a blackbox manner because the program used is not made visible anywhere. Perhaps windows has the equivalent of strace, or perhaps we can use a timer program to see that the speed has improved.
will@WILLIAMMART8251:~/workspace/cli$ hyperfine --warmup 1 'GH_BROWSER=wslview gh repo view --web'
Benchmark 1: GH_BROWSER=wslview gh repo view --web
Time (mean ± σ): 1.862 s ± 0.190 s [User: 0.101 s, System: 0.292 s]
Range (min … max): 1.742 s … 2.365 s 10 runs
will@WILLIAMMART8251:~/workspace/cli$ hyperfine --ignore-failure --warmup 1 'GH_BROWSER=explorer.exe gh repo view --web'
Benchmark 1: GH_BROWSER=explorer.exe gh repo view --web
Time (mean ± σ): 683.1 ms ± 112.0 ms [User: 46.9 ms, System: 51.6 ms]
Range (min … max): 600.5 ms … 965.6 ms 10 runs
Warning: Ignoring non-zero exit code.
We can see that using explorer.exe is much faster (though it was exiting with code 1 which I had to ignore even though the webpage was opening). @spenserblack did you do anything different than setting GH_BROWSER=explorer.exe when testing this out?
will@WILLIAMMART8251:~/workspace/cli$ GH_BROWSER=explorer.exe gh repo view --web
Opening github.com/cli/cli in your browser.
exit status 1
@spenserblack did you do anything different than setting GH_BROWSER=explorer.exe when testing this out?
I had only done GH_BROWSER=explorer.exe. I didn't notice that explorer.exe would return status 1, but now I see that that's indeed the case. Seems to be an issue with running explorer.exe in WSL: microsoft/WSL#6565. It also doesn't seem to be limited to URLs -- explorer.exe . also returns 1 (but succeeds in opening the current directory). Either my Googling skills are bad, or it's really hard to find any documentation about explorer.exe and why it would return an error code here 😅
Just informally testing by running commands, I see that cmd.exe /c start URL has roughly similar performance gains. Perhaps gh-browser can be refactored so that providers can be passed additional arguments?
wslviewcan use cmd.exe over powershell.exe, which should be faster AFAIK, by setting WSLVIEW_DEFAULT_ENGINE. But it seems like it's not available on my device. On Ubuntu 24, apt lists wslu 3.2.3, and the default engine override isn't available until 4.0.0.
If you want to try a map[string][]string where the []string contains extra args we could give that a go I guess? But maybe we should use --skip-validation-check then instead of cmd.exe? Do you have a breakdown of the time spent in the registry vs the lookup? Suspect the latter is the majority?
I can try to break it down later, but I'm fairly certain that most of the slowdown occurs when wslview, by default, starts a powershell.exe process. For whatever reason, powershell.exe seems to typically take a while to start up.
I actually had to deal with this in another project where I was trying to query the registry. At first I used a powershell.exe subprocess since PowerShell had some useful cmdlets. But I found a direct call to reg.exe was much faster. I concluded that calling powershell.exe as a subprocess should be avoided if there's an alternative.
So in summary, I think the real issue is that powershell.exe gets called. But I'm not too fond of the registry query or redundant HTTP call, either 😅
@williammartin@spenserblack : catching up on cli/browser#14 and the commentary here, is there a reasonable way that we can detect and handle WSL Linux situations more specifically?
Looking at the draft PR above, the non-exported openBrowser logic is taking advantage of Go build constraints to differentiate linux from windows, which is a gray area for WSL. I ask to see if there is some other way to detect these scenarios if possible.
From microsoft/WSL#423, it seems like there checking for /proc/version is semi-official way from the WSL perspective. I don't think there is anything in Go runtime to distinguish WSL. 🤔
Given I am on Windows When I invoke a command that opens the browser Thenexplorer.exe is used if it is on the PATH
Notes
It may not be possible to validate the A/C in a blackbox manner because the program used is not made visible anywhere. Perhaps windows has the equivalent of strace, or perhaps we can use a timer program to see that the speed has improved.
Putting a finer point on this, here are all the commands with --web flag that will be affected:
is there a reasonable way that we can detect and handle WSL Linux situations more specifically?
I believe that one of the best ways (in terms of both reliability and simplicity) is to check for the presence of one of the WSL_* environment variables. WSL_DISTRO_NAME, which reports the Linux distribution name, should be a good candidate, but I'll investigate a bit more.
Regarding the build constraints, I'd say that the logic should fall under Linux, since the user will most likely be executing a Linux binary of gh from within WSL.
I suppose they could execute gh.exe from within the subsystem. I've never tried that out, but I'd be curious if gh.exe is even usable in the subsystem. For example, the resolution to the absolute path is different in the subsystem, so it might fail to read config files. So IMO executing the Windows binary, gh.exe, from within the Linux subsystem is a very unusual edge-case that might not need to be handled.
On reflection, I'm a bit concerned about cmd.exe /c start opening up some avenues for command injection, in that openBrowser takes a bare string. Consumers shouldn't be passing arbitrary arguments to openBrowser but people do what people do. Not sure if / how to mitigate that.
is there a reasonable way that we can detect and handle WSL Linux situations more specifically?
Is there some advantage you can see by doing that? I can't think of a way in which the behaviour would change given this knowledge.,
Regarding the build constraints, I'd say that the logic should fall under Linux, since the user will most likely be executing a Linux binary of gh from within WSL.
That's currently what happens with the logic living under _linux.go.
On reflection, I'm a bit concerned about cmd.exe /c start opening up some avenues for command injection, in that openBrowser takes a bare string. Consumers shouldn't be passing arbitrary arguments to openBrowser but people do what people do.
Thanks for bringing up security -- I was so focused on speed that I wasn't really considering that. I did a quick check of wslview's code once more and found that it can also run arbitrary .exes when given a Windows-style path (starts with LETTER:). Confirmed this by passing the absolute Windows path to powershell.exe to wslview. So this concern may also exist in another form by attempting to use wslview.
Thinking in a different direction, how do we feel about this:
Detect if Linux is running in WSL
If so, query the registry for the host Windows instance's default browser
Translate the result, which will be a Windows path to the browser and some CLI options. For example, for chrome, this is C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1, and will need to be translated to /mnt/c/Program Files/.../chrome.exe.
I feel like this would be a security win, as it will pass the (maybe) URL directly to the browser's executable instead of having Windows try to start it with either cmd.exe or powershell.exe, as wslview currently does.
The downside is that it adds a lot of complexity for this one case. This would be much easier if browser installations just added the browser to PATH by default 😩
// providersForUrl returns a slice of possible providers to open a browser on linux. The// order is from most to least preferred.funcprovidersForUrl(urlstring) []string {
base:= []string{"xdg-open", "x-www-browser", "www-browser"}
// explorer.exe is available in WSL. It can open http:// and https://, but not// file://. explorer.exe is preferred over wslview for performance and to avoid// redundant requests.ifstrings.HasPrefix(url, "http://") ||strings.HasPrefix(url, "https://") {
base=append(base, "explorer.exe", "wslview")
} else {
base=append(base, "wslview")
}
returnbase
}
cli/browser has separate build constraints for darwin, freebsd, linux, netbsd, openbsd, windows
go1.22 has various other options I'm unsure if they fall under linux if not specified including aix, android, dragonfly, illumos, ios, js, plan9, solaris, wasip1
if we proceed with checking for explorer.exe or some other Windows-only command available in WSL without better targeting, we're introducing logic that will run and maybe affect other distributions
I know openBrowser will check to see if they're on the path before trying to use them. I suppose I'm trying to limit the scope of changes to only the affected OS even though it shouldn't hopefully be an issue for the others.
They use rundll32.exe url.dll,FileProtocolHJandler URL. It does seem pretty fast. I'm not knowledgeable enough with rundll32.exe, though, so I can't say for certain if it should be included in cli/browser. But, as support for WSL expands, perhaps WSL support should not be a concern of cli/browser (except for keeping the wslview option), and we should trust xdg-open and the other providers to have support in the future?
Personally, I think I'll be configuring my environment to point towards Windows executables where it makes sense, rather than relying on interop tools to do that for me.
if we proceed with checking for explorer.exe or some other Windows-only command available in WSL without better targeting, we're introducing logic that will run and maybe affect other distributions
Right, but the current approach (which it seems like we're moving away from anyway) only puts precedence for explorer.exe over any distribution that has wslview, which we can probably assume is only WSL? Maybe that's naive?
It does seem like we could take some steps though to more tightly scope this for future maintainers.
Wow, I just realized that recent versions of xdg-open seem to already support WSL:
Sorry, are you saying that at some point you would expect xdg-open to be on your WSL distribution?
Sorry, are you saying that at some point you would expect xdg-open to be on your WSL distribution?
I actually already do have xdg-open on my current WSL instance (running Ubuntu). This whole time I've assumed that gh was executing wslview for me -- and while that's true, it wasn't the whole story. I've recently realized it was executing xdg-open, since that has higher priority. But I had BROWSER=wslview set, so xdg-open would then execute wslview 😅
I can't speak for other users, but I've personally become happy with the workaround I've just implemented, which is to set BROWSER to the path of a symbolic link that points to my browser.exe. It's a lot faster and I feel a bit safer now that I know that the URL will 100% be passed to the browser.
Is there anything more you think we should do here then?
One, perhaps naive, idea I had was to detect if the linux system is running in WSL as @andyfeller recommended. If so, add the standard Windows install paths to chrome.exe, firefox.exe, iexplorer.exe, etc. to the list of providers. Again, this goes back to the idea that it's safer to not throw cmd.exe (or powershell.exe) into the mix if it can be avoided (it's also faster). It might be dangerous with certain URL values. Since some domains are also common extensions (like .sh), I feel like there's a slight chance someone forgets the protocol and then gets a nasty surprise when wslview opens ./some-dangerous-script.sh instead of https://some-dangerous-script.sh.
Thanks for your thoughts. For the moment I think I'm content to leave things as-is, though I appreciate your concern about wslview opening .sh files. If we hear more about slowness on WSL I'm happy to revisit, but given this is an area that it's hard to have a lot of confidence in changing, I'd like to hear more evidence this is a real pain that needs to be addressed.
Maybe we should consider reopening this as the wslu project is being discontinued / repo archived. For me a tiny wrapper script around explorer.exe to avoid the non-zero exit status feels very robust and fast.
Activity
williammartin commentedon Feb 19, 2025
Acceptance Criteria
Given I am on Windows
When I invoke a command that opens the browser
Then
explorer.exeis used if it is on the PATHNotes
It may not be possible to validate the A/C in a blackbox manner because the program used is not made visible anywhere. Perhaps windows has the equivalent of strace, or perhaps we can use a timer program to see that the speed has improved.
williammartin commentedon Feb 19, 2025
@spenserblack any thoughts on how I might blackbox validate this? I don't know much about powershell / wsl.
williammartin commentedon Feb 19, 2025
Using hyperfine:
We can see that using
explorer.exeis much faster (though it was exiting with code 1 which I had to ignore even though the webpage was opening). @spenserblack did you do anything different than settingGH_BROWSER=explorer.exewhen testing this out?spenserblack commentedon Feb 19, 2025
I had only done
GH_BROWSER=explorer.exe. I didn't notice thatexplorer.exewould return status 1, but now I see that that's indeed the case. Seems to be an issue with runningexplorer.exein WSL: microsoft/WSL#6565. It also doesn't seem to be limited to URLs --explorer.exe .also returns 1 (but succeeds in opening the current directory). Either my Googling skills are bad, or it's really hard to find any documentation aboutexplorer.exeand why it would return an error code here 😅spenserblack commentedon Feb 19, 2025
Just informally testing by running commands, I see that
cmd.exe /c start URLhas roughly similar performance gains. Perhaps gh-browser can be refactored so that providers can be passed additional arguments?wslviewcan usecmd.exeoverpowershell.exe, which should be faster AFAIK, by settingWSLVIEW_DEFAULT_ENGINE. But it seems like it's not available on my device. On Ubuntu 24,aptlistswslu 3.2.3, and the default engine override isn't available until 4.0.0.williammartin commentedon Feb 19, 2025
If you want to try a
map[string][]stringwhere the[]stringcontains extra args we could give that a go I guess? But maybe we should use--skip-validation-checkthen instead ofcmd.exe? Do you have a breakdown of the time spent in the registry vs the lookup? Suspect the latter is the majority?spenserblack commentedon Feb 19, 2025
I can try to break it down later, but I'm fairly certain that most of the slowdown occurs when
wslview, by default, starts apowershell.exeprocess. For whatever reason,powershell.exeseems to typically take a while to start up.I actually had to deal with this in another project where I was trying to query the registry. At first I used a
powershell.exesubprocess since PowerShell had some useful cmdlets. But I found a direct call toreg.exewas much faster. I concluded that callingpowershell.exeas a subprocess should be avoided if there's an alternative.So in summary, I think the real issue is that
powershell.exegets called. But I'm not too fond of the registry query or redundant HTTP call, either 😅williammartin commentedon Feb 19, 2025
Sure, well let's give it a go with
cmd.exe /c startwith the samemap[string][string]idea.andyfeller commentedon Feb 19, 2025
@williammartin @spenserblack : catching up on cli/browser#14 and the commentary here, is there a reasonable way that we can detect and handle WSL Linux situations more specifically?
Looking at the draft PR above, the non-exported
openBrowserlogic is taking advantage of Go build constraints to differentiate linux from windows, which is a gray area for WSL. I ask to see if there is some other way to detect these scenarios if possible.From microsoft/WSL#423, it seems like there checking for
/proc/versionis semi-official way from the WSL perspective. I don't think there is anything in Go runtime to distinguish WSL. 🤔andyfeller commentedon Feb 19, 2025
Putting a finer point on this, here are all the commands with
--webflag that will be affected:gh auth login [flags]gh codespace code [flags]gh codespace create [flags]gh codespace list [flags]gh extension search [<query>] [flags]gh gist create [<filename>... | -] [flags]gh gist view [<id> | <url>] [flags]gh issue comment {<number> | <url>} [flags]gh issue create [flags]gh issue list [flags]gh issue view {<number> | <url>} [flags]gh label list [flags]gh pr checks [<number> | <url> | <branch>] [flags]gh pr comment [<number> | <url> | <branch>] [flags]gh pr create [flags]gh pr diff [<number> | <url> | <branch>] [flags]gh pr list [flags]gh pr view [<number> | <url> | <branch>] [flags]gh project list [flags]gh project view [<number>] [flags]gh release view [<tag>] [flags]gh repo autolink list [flags]gh repo license view {<license-key> | <SPDX-ID>} [flags]gh repo view [<repository>] [flags]gh ruleset check [<branch>] [flags]gh ruleset list [flags]gh ruleset view [<ruleset-id>] [flags]gh run view [<run-id>] [flags]gh search code <query> [flags]gh search commits [<query>] [flags]gh search issues [<query>] [flags]gh search prs [<query>] [flags]gh search repos [<query>] [flags]gh workflow view [<workflow-id> | <workflow-name> | <filename>] [flags]spenserblack commentedon Feb 19, 2025
I believe that one of the best ways (in terms of both reliability and simplicity) is to check for the presence of one of the
WSL_*environment variables.WSL_DISTRO_NAME, which reports the Linux distribution name, should be a good candidate, but I'll investigate a bit more.Regarding the build constraints, I'd say that the logic should fall under Linux, since the user will most likely be executing a Linux binary of
ghfrom within WSL.I suppose they could execute
gh.exefrom within the subsystem. I've never tried that out, but I'd be curious ifgh.exeis even usable in the subsystem. For example, the resolution to the absolute path is different in the subsystem, so it might fail to read config files. So IMO executing the Windows binary,gh.exe, from within the Linux subsystem is a very unusual edge-case that might not need to be handled.williammartin commentedon Feb 20, 2025
On reflection, I'm a bit concerned about
cmd.exe /c startopening up some avenues for command injection, in thatopenBrowsertakes a bare string. Consumers shouldn't be passing arbitrary arguments toopenBrowserbut people do what people do. Not sure if / how to mitigate that.Is there some advantage you can see by doing that? I can't think of a way in which the behaviour would change given this knowledge.,
That's currently what happens with the logic living under
_linux.go.spenserblack commentedon Feb 20, 2025
Thanks for bringing up security -- I was so focused on speed that I wasn't really considering that. I did a quick check of
wslview's code once more and found that it can also run arbitrary.exes when given a Windows-style path (starts withLETTER:). Confirmed this by passing the absolute Windows path topowershell.exetowslview. So this concern may also exist in another form by attempting to usewslview.Thinking in a different direction, how do we feel about this:
C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1, and will need to be translated to/mnt/c/Program Files/.../chrome.exe.I feel like this would be a security win, as it will pass the (maybe) URL directly to the browser's executable instead of having Windows try to start it with either
cmd.exeorpowershell.exe, aswslviewcurrently does.The downside is that it adds a lot of complexity for this one case. This would be much easier if browser installations just added the browser to
PATHby default 😩andyfeller commentedon Feb 20, 2025
@williammartin : Let me talk through the concern citing this section draft PR:
cli/browserhas separate build constraints fordarwin,freebsd,linux,netbsd,openbsd,windowsgo1.22has various other options I'm unsure if they fall underlinuxif not specified includingaix,android,dragonfly,illumos,ios,js,plan9,solaris,wasip1explorer.exeor some other Windows-only command available in WSL without better targeting, we're introducing logic that will run and maybe affect other distributionsI know
openBrowserwill check to see if they're on the path before trying to use them. I suppose I'm trying to limit the scope of changes to only the affected OS even though it shouldn't hopefully be an issue for the others.spenserblack commentedon Feb 20, 2025
Wow, I just realized that recent versions of
xdg-openseem to already support WSL:https://gitlab.freedesktop.org/xdg/xdg-utils/-/blob/503356e79c7357344fe58fe5183dafef93e646dc/scripts/xdg-utils-common.in#L398
https://gitlab.freedesktop.org/xdg/xdg-utils/-/blob/503356e79c7357344fe58fe5183dafef93e646dc/scripts/xdg-open.in#L458
They use
rundll32.exe url.dll,FileProtocolHJandler URL. It does seem pretty fast. I'm not knowledgeable enough withrundll32.exe, though, so I can't say for certain if it should be included incli/browser. But, as support for WSL expands, perhaps WSL support should not be a concern ofcli/browser(except for keeping thewslviewoption), and we should trustxdg-openand the other providers to have support in the future?Personally, I think I'll be configuring my environment to point towards Windows executables where it makes sense, rather than relying on interop tools to do that for me.
williammartin commentedon Feb 20, 2025
Right, but the current approach (which it seems like we're moving away from anyway) only puts precedence for
explorer.exeover any distribution that haswslview, which we can probably assume is onlyWSL? Maybe that's naive?It does seem like we could take some steps though to more tightly scope this for future maintainers.
Sorry, are you saying that at some point you would expect
xdg-opento be on your WSL distribution?spenserblack commentedon Feb 20, 2025
I actually already do have
xdg-openon my current WSL instance (running Ubuntu). This whole time I've assumed thatghwas executingwslviewfor me -- and while that's true, it wasn't the whole story. I've recently realized it was executingxdg-open, since that has higher priority. But I hadBROWSER=wslviewset, soxdg-openwould then executewslview😅williammartin commentedon Feb 20, 2025
Haha oh dear. Is there anything more you think we should do here then?
spenserblack commentedon Feb 20, 2025
I can't speak for other users, but I've personally become happy with the workaround I've just implemented, which is to set
BROWSERto the path of a symbolic link that points to mybrowser.exe. It's a lot faster and I feel a bit safer now that I know that the URL will 100% be passed to the browser.One, perhaps naive, idea I had was to detect if the linux system is running in WSL as @andyfeller recommended. If so, add the standard Windows install paths to
chrome.exe,firefox.exe,iexplorer.exe, etc. to the list of providers. Again, this goes back to the idea that it's safer to not throwcmd.exe(orpowershell.exe) into the mix if it can be avoided (it's also faster). It might be dangerous with certain URL values. Since some domains are also common extensions (like.sh), I feel like there's a slight chance someone forgets the protocol and then gets a nasty surprise whenwslviewopens./some-dangerous-script.shinstead ofhttps://some-dangerous-script.sh.williammartin commentedon Feb 21, 2025
Thanks for your thoughts. For the moment I think I'm content to leave things as-is, though I appreciate your concern about
wslviewopening.shfiles. If we hear more about slowness on WSL I'm happy to revisit, but given this is an area that it's hard to have a lot of confidence in changing, I'd like to hear more evidence this is a real pain that needs to be addressed.If you have strong feelings, please reopen!
hansfn commentedon Aug 17, 2025
Maybe we should consider reopening this as the wslu project is being discontinued / repo archived. For me a tiny wrapper script around explorer.exe to avoid the non-zero exit status feels very robust and fast.