From d2229aba20fad96a410aa070ad63660dd12743f7 Mon Sep 17 00:00:00 2001 From: Josh Godsiff Date: Fri, 27 Mar 2026 14:32:53 +1100 Subject: [PATCH] boxcli: fix VS Code binary path resolution on macOS The WSL fix in 4ba6fced constructs the editor binary path as $VSCODE_CWD/bin/code. On WSL, VSCODE_CWD points to the VS Code installation directory, so this works. On macOS however, VSCODE_CWD is set to the workspace directory, producing a bogus path like /Users/user/my-project/bin/code which does not exist. Add an os.Stat check so the constructed path is only used when the binary actually exists there, falling back to the bare command name (resolved via PATH) otherwise. Extract the logic into a standalone resolveEditorBinary function with tests covering both cases. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/boxcli/integrate.go | 24 +++++++--- internal/boxcli/integrate_test.go | 77 +++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 internal/boxcli/integrate_test.go diff --git a/internal/boxcli/integrate.go b/internal/boxcli/integrate.go index d5ee89d2b5d..4b64c5b34f1 100644 --- a/internal/boxcli/integrate.go +++ b/internal/boxcli/integrate.go @@ -124,12 +124,7 @@ func runIntegrateVSCodeCmd(cmd *cobra.Command, flags integrateCmdFlags) error { return err } // Open editor with devbox shell environment - cmndName := flags.ideName - cwd, ok := os.LookupEnv("VSCODE_CWD") - if ok { - // Specify full path to avoid running the `code` shell script from VS Code Server, which fails under WSL - cmndName = cwd + "/bin/" + cmndName - } + cmndName := resolveEditorBinary(flags.ideName) cmnd := exec.Command(cmndName, message.ConfigDir) cmnd.Env = append(cmnd.Env, envVars...) var outb, errb bytes.Buffer @@ -145,6 +140,23 @@ func runIntegrateVSCodeCmd(cmd *cobra.Command, flags integrateCmdFlags) error { return nil } +// resolveEditorBinary returns the path to the editor binary. On WSL, +// VSCODE_CWD points to the VS Code installation directory, so we can +// construct an absolute path to avoid the VS Code Server shell script. +// On macOS, VSCODE_CWD is the workspace directory, so the constructed +// path won't exist and we fall back to the bare command name. +func resolveEditorBinary(ideName string) string { + cwd, ok := os.LookupEnv("VSCODE_CWD") + if !ok { + return ideName + } + fullPath := cwd + "/bin/" + ideName + if _, err := os.Stat(fullPath); err == nil { + return fullPath + } + return ideName +} + type debugMode struct { enabled bool } diff --git a/internal/boxcli/integrate_test.go b/internal/boxcli/integrate_test.go new file mode 100644 index 00000000000..1cc006af9a3 --- /dev/null +++ b/internal/boxcli/integrate_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 Jetify Inc. and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package boxcli + +import ( + "os" + "path/filepath" + "testing" +) + +func TestResolveEditorBinary(t *testing.T) { + // Create a temp directory with a bin/code binary to simulate + // a VS Code installation (e.g., WSL's VSCODE_CWD). + installDir := t.TempDir() + binDir := filepath.Join(installDir, "bin") + if err := os.MkdirAll(binDir, 0o755); err != nil { + t.Fatal(err) + } + codePath := filepath.Join(binDir, "code") + if err := os.WriteFile(codePath, []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatal(err) + } + + // A directory that does NOT contain bin/code, simulating + // macOS where VSCODE_CWD is the workspace directory. + workspaceDir := t.TempDir() + + tests := []struct { + name string + vscodeCWD string // empty means unset + ideName string + want string + }{ + { + name: "VSCODE_CWD unset falls back to bare name", + ideName: "code", + want: "code", + }, + { + name: "VSCODE_CWD with valid binary uses full path", + vscodeCWD: installDir, + ideName: "code", + want: filepath.Join(installDir, "bin", "code"), + }, + { + name: "VSCODE_CWD without binary falls back to bare name", + vscodeCWD: workspaceDir, + ideName: "code", + want: "code", + }, + { + name: "non-default IDE name resolves correctly", + vscodeCWD: workspaceDir, + ideName: "cursor", + want: "cursor", + }, + } + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + if testCase.vscodeCWD != "" { + t.Setenv("VSCODE_CWD", testCase.vscodeCWD) + } else { + os.Unsetenv("VSCODE_CWD") + } + + got := resolveEditorBinary(testCase.ideName) + if got != testCase.want { + t.Errorf( + "resolveEditorBinary(%q) = %q, want %q", + testCase.ideName, got, testCase.want, + ) + } + }) + } +}