From 725a88c1c306ec86d824e4c2efdc9137ba9bf4ab Mon Sep 17 00:00:00 2001 From: Alec Fong Date: Wed, 28 Jan 2026 01:50:57 -0800 Subject: [PATCH] Fix WSL exec format error when opening editors In WSL, Windows .exe files cannot be executed directly - they need to go through cmd.exe. This fixes the "Exec format error" when running `brev open cursor` or similar commands in WSL. Changes: - Add isWSL() helper to detect Windows Subsystem for Linux - Add wslPathToWindows() to convert /mnt/c/... paths to C:\... - Add runWindowsExeInWSL() to run Windows executables via cmd.exe - Update runVsCodeCommand, runCursorCommand, runWindsurfCommand to detect WSL and use cmd.exe for Windows executables This allows users in WSL to seamlessly open VS Code, Cursor, and Windsurf installed on the Windows side. --- pkg/util/util.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/pkg/util/util.go b/pkg/util/util.go index d9004e1d..299ba5b1 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" breverrors "github.com/brevdev/brev-cli/pkg/errors" @@ -13,6 +14,53 @@ import ( "github.com/hashicorp/go-multierror" ) +// isWSL returns true if running in Windows Subsystem for Linux +func isWSL() bool { + if runtime.GOOS != "linux" { + return false + } + // Check for WSL-specific indicators + if _, err := os.Stat("/proc/sys/fs/binfmt_misc/WSLInterop"); err == nil { + return true + } + // Also check /proc/version for "microsoft" or "WSL" + if data, err := os.ReadFile("/proc/version"); err == nil { + lower := strings.ToLower(string(data)) + if strings.Contains(lower, "microsoft") || strings.Contains(lower, "wsl") { + return true + } + } + return false +} + +// wslPathToWindows converts a WSL path like /mnt/c/Users/... to C:\Users\... +func wslPathToWindows(wslPath string) string { + if strings.HasPrefix(wslPath, "/mnt/") && len(wslPath) > 6 { + // Extract drive letter: /mnt/c/... -> c + drive := strings.ToUpper(string(wslPath[5])) + // Get rest of path: /mnt/c/Users/... -> /Users/... + rest := wslPath[6:] + // Convert to Windows path: C:\Users\... + windowsPath := drive + ":" + strings.ReplaceAll(rest, "/", "\\") + return windowsPath + } + return wslPath +} + +// runWindowsExeInWSL runs a Windows executable from WSL using cmd.exe +func runWindowsExeInWSL(exePath string, args []string) ([]byte, error) { + // Convert WSL path to Windows path + windowsPath := wslPathToWindows(exePath) + + // Build the command string for cmd.exe + // We need to quote the path and args properly for Windows + cmdArgs := []string{"/c", windowsPath} + cmdArgs = append(cmdArgs, args...) + + cmd := exec.Command("cmd.exe", cmdArgs...) // #nosec G204 + return cmd.CombinedOutput() +} + // This package should only be used as a holding pattern to be later moved into more specific packages func MapAppend(m map[string]interface{}, n ...map[string]interface{}) map[string]interface{} { @@ -205,6 +253,15 @@ func runManyCursorCommand(cursorpaths []string, args []string) ([]byte, error) { } func runVsCodeCommand(vscodepath string, args []string) ([]byte, error) { + // In WSL, Windows .exe files need to be run through cmd.exe + if isWSL() && (strings.HasSuffix(vscodepath, ".exe") || strings.HasPrefix(vscodepath, "/mnt/")) { + res, err := runWindowsExeInWSL(vscodepath, args) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + return res, nil + } + cmd := exec.Command(vscodepath, args...) // #nosec G204 res, err := cmd.CombinedOutput() if err != nil { @@ -214,6 +271,15 @@ func runVsCodeCommand(vscodepath string, args []string) ([]byte, error) { } func runCursorCommand(cursorpath string, args []string) ([]byte, error) { + // In WSL, Windows .exe files need to be run through cmd.exe + if isWSL() && (strings.HasSuffix(cursorpath, ".exe") || strings.HasPrefix(cursorpath, "/mnt/")) { + res, err := runWindowsExeInWSL(cursorpath, args) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + return res, nil + } + cmd := exec.Command(cursorpath, args...) // #nosec G204 res, err := cmd.CombinedOutput() if err != nil { @@ -236,6 +302,15 @@ func runManyWindsurfCommand(windsurfpaths []string, args []string) ([]byte, erro } func runWindsurfCommand(windsurfpath string, args []string) ([]byte, error) { + // In WSL, Windows .exe files need to be run through cmd.exe + if isWSL() && (strings.HasSuffix(windsurfpath, ".exe") || strings.HasPrefix(windsurfpath, "/mnt/")) { + res, err := runWindowsExeInWSL(windsurfpath, args) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + return res, nil + } + cmd := exec.Command(windsurfpath, args...) // #nosec G204 res, err := cmd.CombinedOutput() if err != nil {