Skip to content

Commit 723b687

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@fdace2e259edc724e584ed22389e388fa4cf0597
1 parent 3a8d294 commit 723b687

3 files changed

Lines changed: 31 additions & 3 deletions

File tree

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"terminal-image": "^4.1.0",
5353
"ts-pattern": "^5.9.0",
5454
"unified": "^11.0.0",
55+
"wsl-utils": "^0.1.0",
5556
"yoga-layout": "^3.2.1",
5657
"zod": "^4.2.1",
5758
"zustand": "^5.0.8"

cli/src/utils/open-url.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import fs from 'fs'
12
import os from 'os'
23

34
import open from 'open'
5+
import { isWsl, powerShellPathFromWsl } from 'wsl-utils'
46

57
import { getCliEnv } from './env'
68
import { logger } from './logger'
@@ -11,12 +13,30 @@ import { logger } from './logger'
1113
* On headless Linux (no DISPLAY or WAYLAND_DISPLAY), calling `open()` spawns
1214
* `xdg-open` which can crash the entire process — even inside a try/catch —
1315
* because the child process may trigger fatal signals. This wrapper detects
14-
* headless environments and skips the call entirely.
16+
* headless environments and skips the call entirely. WSL is exempt: there
17+
* `open()` goes through powershell.exe, which needs no display.
18+
*
19+
* On WSL, `open()` spawns powershell.exe from the Windows mount. If Windows
20+
* interop is disabled (no access to /mnt/c), that spawn fails with ENOENT.
21+
* Under Bun the failure is delivered before the `open()` promise resolves and
22+
* cannot be caught from here — not by try/catch, and not by an 'error'
23+
* listener attached afterward — so the only reliable defense is checking that
24+
* powershell.exe exists before calling `open()`. `wsl-utils` is what `open`
25+
* itself uses to build the path, so the check matches its behavior exactly.
1526
*
1627
* @returns `true` if the browser was (likely) opened, `false` if skipped.
1728
*/
1829
export async function safeOpen(url: string): Promise<boolean> {
19-
if (os.platform() === 'linux') {
30+
if (isWsl) {
31+
const powershellPath = await powerShellPathFromWsl()
32+
if (!fs.existsSync(powershellPath)) {
33+
logger.warn(
34+
{ powershellPath },
35+
'WSL detected but powershell.exe is not accessible (Windows interop disabled?). Skipping browser open.',
36+
)
37+
return false
38+
}
39+
} else if (os.platform() === 'linux') {
2040
const env = getCliEnv()
2141
const hasDisplay = Boolean(env.DISPLAY || env.WAYLAND_DISPLAY)
2242
if (!hasDisplay) {
@@ -28,7 +48,13 @@ export async function safeOpen(url: string): Promise<boolean> {
2848
}
2949

3050
try {
31-
await open(url)
51+
const subprocess = await open(url)
52+
// With the default `wait: false`, spawn failures can surface on the
53+
// child's 'error' event after the promise resolves; without a listener
54+
// they become uncaught exceptions that kill the process.
55+
subprocess.once('error', (err) => {
56+
logger.error(err, 'Failed to open browser')
57+
})
3258
return true
3359
} catch (err) {
3460
logger.error(err, 'Failed to open browser')

0 commit comments

Comments
 (0)