From 0fba792df72811c53fa85ee251a47dba66299125 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:26:29 +0000 Subject: [PATCH 1/6] Initial plan From 5d2e0371c267929e8637ab15dbd3dd112a5bfb76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:30:31 +0000 Subject: [PATCH 2/6] Fix vfox upgrade command on Windows to pass all arguments and check for errors Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> --- cmd/commands/upgrade_win.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/cmd/commands/upgrade_win.go b/cmd/commands/upgrade_win.go index 9c069b6c..87cf1224 100644 --- a/cmd/commands/upgrade_win.go +++ b/cmd/commands/upgrade_win.go @@ -58,11 +58,29 @@ func runAsAdmin() error { return err } + // Build arguments string from os.Args (skip the executable name) + // This ensures all flags like --debug are passed through + args := "" + if len(os.Args) > 1 { + // Join all arguments starting from index 1 + for i, arg := range os.Args[1:] { + if i > 0 { + args += " " + } + // Quote arguments that contain spaces + if len(arg) > 0 && (arg[0] == '-' || !containsSpace(arg)) { + args += arg + } else { + args += "\"" + arg + "\"" + } + } + } + verb := "runas" cwd, _ := syscall.UTF16PtrFromString(".") - arg, _ := syscall.UTF16PtrFromString(SelfUpgradeName) + arg, _ := syscall.UTF16PtrFromString(args) run := windows.NewLazySystemDLL("shell32.dll").NewProc("ShellExecuteW") - run.Call( + ret, _, _ := run.Call( 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(verb))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(exePath))), @@ -70,6 +88,19 @@ func runAsAdmin() error { uintptr(unsafe.Pointer(cwd)), 1, ) + // ShellExecuteW returns a value > 32 on success + if ret <= 32 { + return syscall.Errno(ret) + } os.Exit(0) return nil } + +func containsSpace(s string) bool { + for _, c := range s { + if c == ' ' { + return true + } + } + return false +} From f2c5ed3de14f244e081e76b2c706a8ee4aba1b1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:32:24 +0000 Subject: [PATCH 3/6] Improve argument quoting logic for Windows upgrade command Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> --- cmd/commands/upgrade_win.go | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/cmd/commands/upgrade_win.go b/cmd/commands/upgrade_win.go index 87cf1224..6455f445 100644 --- a/cmd/commands/upgrade_win.go +++ b/cmd/commands/upgrade_win.go @@ -20,6 +20,7 @@ package commands import ( "os" + "strings" "syscall" "unsafe" @@ -63,17 +64,18 @@ func runAsAdmin() error { args := "" if len(os.Args) > 1 { // Join all arguments starting from index 1 - for i, arg := range os.Args[1:] { - if i > 0 { - args += " " - } - // Quote arguments that contain spaces - if len(arg) > 0 && (arg[0] == '-' || !containsSpace(arg)) { - args += arg + quotedArgs := make([]string, 0, len(os.Args)-1) + for _, arg := range os.Args[1:] { + // Quote arguments that contain spaces or special characters + if strings.ContainsAny(arg, " \t\n\"") { + // Escape quotes and wrap in quotes + escaped := strings.ReplaceAll(arg, "\"", "\\\"") + quotedArgs = append(quotedArgs, "\""+escaped+"\"") } else { - args += "\"" + arg + "\"" + quotedArgs = append(quotedArgs, arg) } } + args = strings.Join(quotedArgs, " ") } verb := "runas" @@ -95,12 +97,3 @@ func runAsAdmin() error { os.Exit(0) return nil } - -func containsSpace(s string) bool { - for _, c := range s { - if c == ' ' { - return true - } - } - return false -} From 49db3ab4c74025cfcd4dfadbf45be2e270d79a3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:34:55 +0000 Subject: [PATCH 4/6] Implement proper Windows command-line argument escaping Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> --- cmd/commands/upgrade_win.go | 52 ++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/cmd/commands/upgrade_win.go b/cmd/commands/upgrade_win.go index 6455f445..19935ccc 100644 --- a/cmd/commands/upgrade_win.go +++ b/cmd/commands/upgrade_win.go @@ -66,14 +66,7 @@ func runAsAdmin() error { // Join all arguments starting from index 1 quotedArgs := make([]string, 0, len(os.Args)-1) for _, arg := range os.Args[1:] { - // Quote arguments that contain spaces or special characters - if strings.ContainsAny(arg, " \t\n\"") { - // Escape quotes and wrap in quotes - escaped := strings.ReplaceAll(arg, "\"", "\\\"") - quotedArgs = append(quotedArgs, "\""+escaped+"\"") - } else { - quotedArgs = append(quotedArgs, arg) - } + quotedArgs = append(quotedArgs, escapeArg(arg)) } args = strings.Join(quotedArgs, " ") } @@ -94,6 +87,47 @@ func runAsAdmin() error { if ret <= 32 { return syscall.Errno(ret) } + // Exit the current process since we've successfully launched the elevated one os.Exit(0) - return nil + return nil // unreachable, but required by function signature +} + +// escapeArg escapes a command-line argument according to Windows rules. +// Based on https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way +func escapeArg(arg string) string { + // If the argument doesn't contain special characters, return as-is + if !strings.ContainsAny(arg, " \t\n\"") { + return arg + } + + // Build the escaped argument + var b strings.Builder + b.WriteByte('"') + + for i := 0; i < len(arg); i++ { + numBackslashes := 0 + + // Count consecutive backslashes + for i < len(arg) && arg[i] == '\\' { + i++ + numBackslashes++ + } + + if i == len(arg) { + // Backslashes at the end need to be doubled (they precede the closing quote) + b.WriteString(strings.Repeat("\\", numBackslashes*2)) + break + } else if arg[i] == '"' { + // Backslashes before a quote need to be doubled, and the quote needs to be escaped + b.WriteString(strings.Repeat("\\", numBackslashes*2)) + b.WriteString("\\\"") + } else { + // Regular backslashes (not before a quote) are literal + b.WriteString(strings.Repeat("\\", numBackslashes)) + b.WriteByte(arg[i]) + } + } + + b.WriteByte('"') + return b.String() } From 1019582b9a6f68b2367bdac43d4bd716d780a456 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:36:55 +0000 Subject: [PATCH 5/6] Fix loop variable issue and improve code clarity in escapeArg Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> --- cmd/commands/upgrade_win.go | 42 +++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/cmd/commands/upgrade_win.go b/cmd/commands/upgrade_win.go index 19935ccc..db8610bb 100644 --- a/cmd/commands/upgrade_win.go +++ b/cmd/commands/upgrade_win.go @@ -61,15 +61,17 @@ func runAsAdmin() error { // Build arguments string from os.Args (skip the executable name) // This ensures all flags like --debug are passed through - args := "" - if len(os.Args) > 1 { - // Join all arguments starting from index 1 - quotedArgs := make([]string, 0, len(os.Args)-1) - for _, arg := range os.Args[1:] { - quotedArgs = append(quotedArgs, escapeArg(arg)) - } - args = strings.Join(quotedArgs, " ") + if len(os.Args) <= 1 { + // No arguments to pass + return syscall.Errno(87) // ERROR_INVALID_PARAMETER + } + + // Join all arguments starting from index 1 + quotedArgs := make([]string, 0, len(os.Args)-1) + for _, arg := range os.Args[1:] { + quotedArgs = append(quotedArgs, escapeArg(arg)) } + args := strings.Join(quotedArgs, " ") verb := "runas" cwd, _ := syscall.UTF16PtrFromString(".") @@ -88,8 +90,9 @@ func runAsAdmin() error { return syscall.Errno(ret) } // Exit the current process since we've successfully launched the elevated one + // This function never returns normally after successful elevation os.Exit(0) - return nil // unreachable, but required by function signature + panic("unreachable") // never reached, but satisfies the compiler } // escapeArg escapes a command-line argument according to Windows rules. @@ -104,27 +107,30 @@ func escapeArg(arg string) string { var b strings.Builder b.WriteByte('"') - for i := 0; i < len(arg); i++ { - numBackslashes := 0 - + for i := 0; i < len(arg); { // Count consecutive backslashes + backslashes := 0 for i < len(arg) && arg[i] == '\\' { + backslashes++ i++ - numBackslashes++ } - if i == len(arg) { + if i >= len(arg) { // Backslashes at the end need to be doubled (they precede the closing quote) - b.WriteString(strings.Repeat("\\", numBackslashes*2)) + b.WriteString(strings.Repeat("\\", backslashes*2)) break - } else if arg[i] == '"' { + } + + if arg[i] == '"' { // Backslashes before a quote need to be doubled, and the quote needs to be escaped - b.WriteString(strings.Repeat("\\", numBackslashes*2)) + b.WriteString(strings.Repeat("\\", backslashes*2)) b.WriteString("\\\"") + i++ } else { // Regular backslashes (not before a quote) are literal - b.WriteString(strings.Repeat("\\", numBackslashes)) + b.WriteString(strings.Repeat("\\", backslashes)) b.WriteByte(arg[i]) + i++ } } From 3d90ec48f09ab06ee79d0a22701d9108db4a9aaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:38:08 +0000 Subject: [PATCH 6/6] Allow vfox upgrade to work without additional arguments Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> --- cmd/commands/upgrade_win.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cmd/commands/upgrade_win.go b/cmd/commands/upgrade_win.go index db8610bb..c018d61c 100644 --- a/cmd/commands/upgrade_win.go +++ b/cmd/commands/upgrade_win.go @@ -61,17 +61,15 @@ func runAsAdmin() error { // Build arguments string from os.Args (skip the executable name) // This ensures all flags like --debug are passed through - if len(os.Args) <= 1 { - // No arguments to pass - return syscall.Errno(87) // ERROR_INVALID_PARAMETER - } - - // Join all arguments starting from index 1 - quotedArgs := make([]string, 0, len(os.Args)-1) - for _, arg := range os.Args[1:] { - quotedArgs = append(quotedArgs, escapeArg(arg)) + args := "" + if len(os.Args) > 1 { + // Join all arguments starting from index 1 + quotedArgs := make([]string, 0, len(os.Args)-1) + for _, arg := range os.Args[1:] { + quotedArgs = append(quotedArgs, escapeArg(arg)) + } + args = strings.Join(quotedArgs, " ") } - args := strings.Join(quotedArgs, " ") verb := "runas" cwd, _ := syscall.UTF16PtrFromString(".") @@ -92,7 +90,7 @@ func runAsAdmin() error { // Exit the current process since we've successfully launched the elevated one // This function never returns normally after successful elevation os.Exit(0) - panic("unreachable") // never reached, but satisfies the compiler + return nil // unreachable but required for compilation } // escapeArg escapes a command-line argument according to Windows rules.