Repository navigation
feat(auth): open the login URL in a Windows browser from WSL - #206
Conversation
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 2 warnings
🔍 Full review · 2 files reviewed
Verification
browserCommandkeeps the explicit--browseroption and the "0" opt-out ahead of the new WSL case, so users can still override the WSL choice.isWSLreturns false right away on non-Linux GOOS, so Windows and macOS behaviour is unchanged.- The rundll32 loop's inner
pshadows the loop variable, but it returns the resolved absolute path, whichTestBrowserCommand_WSLasserts. TestBrowserCommand_WSLsets PATH to the temp dir only, so a realwslviewon the host cannot change its result.
The new unit tests TestBrowserCommand_WSL (Linux only, stubbed rundll32.exe on PATH) and TestIsWSL_NotWSL cover detection and command selection, and run through make test. Nothing covers the case where no Windows opener is found, or opening a URL from real WSL; the PR says this needs a manual check.
Review details
- Commit: 80b7a16
- Model: claude-opus-5-5
Review 1 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning
🔁 Incremental · 2 files reviewed
Verification
hasDisplay()now returns true for WSL only whenwslBrowserCommand()finds wslview or rundll32.exe, so the xdg-open fallback is never reached with DISPLAY unset.- A Docker Desktop container with the microsoft kernel, no
/mnt/cand no wslview now getshasDisplay()false and is not offered a browser login. - If
openURLfails,runBrowserLogin(auth_login.go:166) falls back to printing the URL, so a failed opener does not block login.
The new TestBrowserCommand_WSLWithoutOpener covers WSL with no opener file on PATH or under /mnt/c, and TestBrowserCommand_WSL covers the case where an opener is found. Nothing covers interop being disabled, or real WSL hosts, which the PR leaves to a manual check.
Review 2 of 10 for this pull request · View the full run
|
📋 PR Summary When running under WSL with Windows interop enabled, the CLI now treats itself as having a display and opens login URLs in a Windows browser using Changes
|
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 1 warning · 1 still open
🔁 Incremental · 4 files reviewed
Outstanding from earlier reviews:
- 🟡 #4225570767 —
commands/auth.go:280: The user is asked a browser prompt that cannot open a browser. —wslBrowserCommand()still only callsexec.LookPath, so with interop disabled and/mnt/cmounted the browser is still offered. The new PHPgetWslBrowser()has the same gap.
Verification
Url::hasDisplay()on WSL now needsgetWslBrowser()to find an opener. A Docker Desktop container, with no/mnt/cand no Windows PATH, therefore gets no display.getDefaultBrowser()checks for the WSL opener before falling back toxdg-open/gnome-open, and only whenisWsl()is true, so plain Linux behaves as before.openUrl()addsurl.dll,FileProtocolHandlerboth whenrundll32.exeis found on PATH and when the/mnt/cabsolute path is used, becausestr_ends_withmatches both.- Go
hasDisplay()now ANDsisWSL()withwslBrowserCommand() != nil, which matches the PHP condition.
The diff adds legacy/tests/Service/UrlTest.php, which mocks Shell::commandExists/execute to cover the rundll32, /mnt/c fallback, wslview and no-opener cases. It also adds OsUtilTest::testIsWsl, which tests env-var detection. No test covers a found opener that cannot execute (interop disabled), and real WSL behaviour still needs the manual check.
Review 3 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Note
Reviewed — No blocking findings · 🔵 1 minor point
🔁 Incremental · 8 files reviewed
🔵 Minor point
commands/root.go:348—makeLegacyCLIWrappernow callswslBrowser()for every legacy invocation. That includes_complete, which runs on every Tab press, pluslist,completion,initand the session-export migrator. Under WSL,wslBrowser()globs binfmt_misc and reads/etc/wsl.conf. It then callsexec.LookPath("wslview")andexec.LookPath("rundll32.exe"), which stat each PATH entry. With the defaultappendWindowsPath=true, PATH holds many/mnt/c/...directories. On WSL2 those are on the drvfs/9p mount, where each stat is slow. Whenwslviewis not installed, the whole PATH is scanned once for it and again for rundll32. Every legacy command and every completion request on WSL now pays this cost, even though only browser-opening commands needWSL_BROWSER. Computing it lazily, or caching it once per process, would avoid the cost.
Verification
- The Go wrapper appends
WSL_BROWSERafteros.Environ(), and Go's exec keeps the last duplicate. So a user-set value is replaced, as the comment says. Url's newConfigconstructor argument is autowired bylegacy/config/services.yaml(autowire: true), and nothing inlegacy/srcbuildsUrlby hand.- The PHP side reads
application.env_prefix, which is the same prefix the wrapper uses (cnf.Application.EnvPrefix) to setWSL_BROWSER. An empty value maps tofalsethrough?:. wslBrowser()returns onlyc[0].Url::openUrladdsurl.dll,FileProtocolHandlerback for any browser ending inrundll32.exe, so the rundll32 path still gets its argument.
New Go tests cover the wsl.conf parsing (TestWSLInteropEnabled) and wslBrowser() with fake binfmt and PATH stubs. The rewritten legacy/tests/Service/UrlTest.php covers the env-var handoff. The CI jobs make test and scripts/test/unit.sh in ci.yml run these tests. Nothing tests real WSL, and no test covers the WSL_BROWSER env injection in CLIWrapper.Exec.
Review 5 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Note
Reviewed — No new issues found · 1 still open
🔁 Incremental · 3 files reviewed
Outstanding from earlier reviews:
- 🔵
commands/root.go:348: Shell completion and every legacy command get slower on WSL2. — makeLegacyCLIWrapper still calls wslBrowser() on every legacy run, including _complete. Trying System32 first skips the PATH scan only when /mnt/c holds rundll32.exe; without it, the binfmt glob, the wsl.conf read and the full PATH scan still happen on every run. (first raised)
Verification
wslBrowserCommandreturns before anyLookPathwhenwslInteropEnabled()is false, so turning interop off now skips the System32 stat as well as the PATH scan.- Every test that calls
setWSLInteropnow pointswslRundll32Pathat a temp file and restores it int.Cleanup, so a real/mnt/con the host can no longer change the test results. - Removing the
wslviewbranch leaves nothing broken: legacyUrl::openUrladdsurl.dll,FileProtocolHandlerfor any browser path ending inrundll32.exe, which is the only kind the Go side now passes. - In
TestBrowserCommand_WSL,rundll32.exeexists both in System32 and on PATH, and the test asserts the System32 one wins, which pins the new lookup order.
The Go behaviour is covered by tests in this change: the TestBrowserCommand_WSL* tests, a new PATH-fallback test, and TestWSLInteropEnabled, all with faked binfmt, wsl.conf and System32 paths. On the PHP side the change only removes the wslview case from UrlTest. The project runs these tests with make test (Go) and PHPUnit (legacy). As the PR description says, nothing in CI runs on real WSL.
Review 6 of 10 for this pull request · View the full run
|
Re the minor point on
🤖 Replied by Claude Code |
4b2b344 to
95786fa
Compare
b23b2be to
f483f6a
Compare
There was a problem hiding this comment.
Note
Reviewed — No blocking findings · 🔵 1 minor point
🔍 Full review · 6 files reviewed
🔵 Minor point
commands/root.go:348—makeLegacyCLIWrappernow always callswslBrowser(), so every legacy command does the WSL lookup before it starts, even though only the login prompt andopenUrluse the result. This is also true of theauth:export-sessionsmigration wrapper. On WSL each command globs binfmt_misc, reads theWSLInteropentry and/etc/wsl.conf, then callsexec.LookPath. Theexec.LookPathcall is the costly part. Whenrundll32.exeis not at/mnt/c/Windows/System32(for example,[automount] rootis changed in wsl.conf), it falls back to searching PATH. The PR itself says that search takes tens of milliseconds under WSL2, and now every legacy invocation pays it. On non-WSL Linux it still reads/proc/sys/kernel/osreleaseon every command. Doing the lookup lazily, or only once per process, avoids that cost.
Verification
browserCommand's newisWSL()case falls through to the xdg-open/gnome-open search whenwslBrowserCommand()returns nil, so non-interop WSL keeps its previous behaviour.- The legacy wrapper always sets
<PREFIX>WSL_BROWSER, even when empty. The PHPgetWslBrowser()treats an empty value as false, so a value inherited from the user's environment cannot enable a browser. - PHP
Urlis autowired throughconfig/services.yaml(thePlatformsh\Cli\Service\resource), so its newConfigconstructor argument needs no manual wiring. The mockedUrlinBrowserLoginCommandTestis unaffected. - The PHP
openUrladds theurl.dll,FileProtocolHandlerargument only for a browser path ending inrundll32.exe. On Windows the'start'branch is still checked first.
New Go unit tests in commands/auth_test.go fake binfmt_misc, wsl.conf and the rundll32.exe stubs. The new legacy/tests/Service/UrlTest.php covers the PHP side. These run in the ci.yml make test and legacy-php jobs. Nothing in CI runs on a real WSL system; the PR says it was checked by hand.
Review details
- Commit: f483f6a
- Model: claude-opus-5-5
Review 7 of 10 for this pull request · View the full run
There was a problem hiding this comment.
Warning
Changes suggested — 🟡 2 warnings
🔁 Incremental · 2 files reviewed
Verification
wslBrowserCommandreads wsl.conf once and passes its contents to bothwslInteropEnabledand the automount root lookup, so the two checks see the same file.filepath.Joinnormalises a root with or without a trailing slash, as the 'custom root without slash' test case covers.exec.LookPathwith an absolute path only stats that file, so the expensive WSL2 search ofPATHis gone from every legacy command start.wslConfValuecompares section and key names case-insensitively and ignores keys from other sections, matching the old interop-only parser.
This push changes the unit tests in commands/auth_test.go: TestWSLBrowser_MountRoot covers the default and custom roots and the removed PATH fallback, TestWSLConfValue covers the parser, and TestWSLInteropEnabled now passes wsl.conf contents directly. They run in make test. Quoted wsl.conf values and non-C: system drives have no tests, and CI has no real WSL.
Review 8 of 10 for this pull request · View the full run
On WSL there is usually no DISPLAY and no xdg-open, so the login prompt was not offered, and login printed the URL instead of opening it. Under WSL, URLs are now opened with wslview if it is installed, or else with Windows' rundll32.exe (as on Windows), found in PATH or in its default location. The local server's 127.0.0.1 address works from Windows, as WSL forwards localhost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… found WSL counted as having a display even without a Windows opener, e.g. with interop disabled, or in a Docker Desktop container, which shares the WSL kernel. The CLI then offered a browser login with no display, and xdg-open could start a terminal browser or fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On WSL, the legacy CLI found no display or xdg-open, so legacy commands did not offer "Log in via a browser?" (with or without Go auth), and the PHP login printed the URL instead of opening it. Match the Go detection: under WSL (from WSL_DISTRO_NAME/WSL_INTEROP or the kernel release), count as having a display if a Windows opener is found, and open URLs with wslview, or else rundll32.exe from PATH or /mnt/c/Windows/System32. The lookup only runs on WSL, when a URL is to be opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With WSL interop off, rundll32.exe still exists under /mnt/c, so the CLI offered "Log in via a browser?", but running it fails (exec format error). Require WSL's binfmt_misc interop entry (WSLInterop or WSLInterop-late) to be enabled before using wslview or rundll32.exe, in Go and in the legacy CLI. Checked on WSL1: the entry exists and is "enabled" with interop on, and is missing with interop off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WSL2 keeps the WSLInterop binfmt_misc entry enabled when wsl.conf sets [interop] enabled=false, so the CLI offered a browser login and then waited about 10 seconds for rundll32.exe to fail. Also check wsl.conf. So that the detection lives in one place, the Go wrapper passes the WSL opener to the legacy CLI in <PREFIX>WSL_BROWSER (always set, empty when there is none), and the PHP Url service uses it instead of its own WSL checks. Checked on Windows 11 with WSL1 (VirtualBox) and WSL2 (QEMU/KVM): with interop on, legacy commands offer the prompt and open Edge; with it off, there's no prompt and login prints the URL at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wrapper looks up the WSL opener for every legacy command. In WSL2, PATH includes Windows directories on a slow mount: searching it for wslview (usually missing) and rundll32.exe took about 70ms, against 6ms for checking /mnt/c/Windows/System32/rundll32.exe. Check that path first, fall back to PATH, and drop wslview, which rundll32.exe makes unneeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…earching PATH The legacy CLI wrapper looks for a Windows browser on every legacy command. When rundll32.exe was not at /mnt/c/Windows/System32 (e.g. wsl.conf sets [automount] root), it fell back to searching PATH, which is slow under WSL2 as PATH includes Windows directories. Read the automount root from wsl.conf (default /mnt/) and only check <root>c/Windows/System32/rundll32.exe. wsl.conf is now read once per lookup, with a shared helper for the [interop] and [automount] keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Go wrapper looked for a Windows browser on every legacy command, to pass it in <PREFIX>WSL_BROWSER, although few commands open URLs. The legacy Url service now finds it itself, only when it needs one, with the same checks as Go: WSL, interop enabled (binfmt_misc and wsl.conf), then rundll32.exe under the wsl.conf automount root, or else in PATH. Since the lookup now only runs before opening a browser, the PATH fallback is restored in Go too, for setups where the Windows drive is not at <root>/c (e.g. mounted via fstab). wsl.conf values may be quoted, so one pair of surrounding quotes is stripped in both Go and PHP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d523d7d to
70b17d7
Compare
On WSL there is usually no
DISPLAYand noxdg-open, so the "Log in via a browser?" prompt was not offered, andloginprinted the URL instead of opening it. Under WSL (detected fromWSL_DISTRO_NAME/WSL_INTEROPor the kernel release), if Windows interop is enabled (WSL'sWSLInteropbinfmt_misc entry, and not disabled in/etc/wsl.conf, which WSL2 needs), the CLI now counts as having a display, and opens URLs with Windows'rundll32.exe url.dll,FileProtocolHandler(as on Windows), from<root>/c/Windows/System32(rootis[automount] rootinwsl.conf, default/mnt/) or elsePATH(searchingPATHis slow in WSL2). The local login server's 127.0.0.1 address works from Windows, as WSL forwards localhost.The legacy CLI finds the opener in the same way, only when it needs one, so legacy commands also offer the prompt in WSL (the legacy CLI decides whether to offer it, with or without Go auth), and the PHP login opens the URL too.
Checked manually on Windows 11 with WSL1 and WSL2 (Ubuntu 24.04), as CI has no WSL. This was before the legacy CLI found the opener itself (Go passed it in an env var).
loginopens the URL in Edge, which reaches the login page, withrundll32.exefrom/mnt/c/Windows/System32and fromPATH.project:list) offers "Log in via a browser?" and opens the URL, with and without Go auth.wsl.conf, there is no prompt andloginprints the URL at once.🤖 Generated with Claude Code