Skip to content

Commit fe996d3

Browse files
authored
Merge pull request cli#13393 from cli/babakks/improve-gh-copilot-error
fix(copilot): hint to run copilot directly when exec fails
2 parents 9b505c3 + 24c7b25 commit fe996d3

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

pkg/cmd/copilot/copilot.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ func runCopilot(opts *CopilotOptions) error {
142142
return nil
143143
}
144144

145-
copilotPath := findCopilotBinary()
146-
if copilotPath == "" {
145+
copilotPath := findCopilotBinaryFunc()
146+
foundInPath := copilotPath != ""
147+
if !foundInPath {
147148
if opts.IO.CanPrompt() {
148149
confirmed, err := opts.Prompter.Confirm("GitHub Copilot CLI is not installed. Would you like to install it?", true)
149150
if err != nil {
@@ -175,12 +176,18 @@ func runCopilot(opts *CopilotOptions) error {
175176
externalCmd.Stderr = opts.IO.ErrOut
176177
externalCmd.Env = append(os.Environ(), "COPILOT_GH=true")
177178

178-
if err := externalCmd.Run(); err != nil {
179+
if err := runExternalCmdFunc(externalCmd); err != nil {
179180
if exitErr, ok := err.(*exec.ExitError); ok {
180181
// We terminate with os.Exit here, preserving the exit code from Copilot CLI,
181182
// and also preventing stdio writes by callers up the stack.
182183
os.Exit(exitErr.ExitCode())
183184
}
185+
if foundInPath {
186+
// We found a `copilot` binary but exec failed, possibly due to
187+
// unusual characters in the path (see https://github.com/cli/cli/issues/13106).
188+
// Suggest running copilot directly as a workaround.
189+
return fmt.Errorf("%w\nFailed to run '%s', try running `copilot` directly without `gh`.", err, copilotPath)
190+
}
184191
return err
185192
}
186193
return nil
@@ -200,6 +207,14 @@ func copilotBinaryPath() string {
200207
return filepath.Join(copilotInstallDir(), binaryName)
201208
}
202209

210+
var runExternalCmdFunc = runExternalCmd
211+
212+
func runExternalCmd(cmd *exec.Cmd) error {
213+
return cmd.Run()
214+
}
215+
216+
var findCopilotBinaryFunc = findCopilotBinary
217+
203218
// findCopilotBinary returns the path to the Copilot CLI binary, if installed,
204219
// with the following order of precedence:
205220
// 1. `copilot` in the PATH

pkg/cmd/copilot/copilot_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"net/http"
1212
"os"
13+
"os/exec"
1314
"path/filepath"
1415
"runtime"
1516
"testing"
@@ -589,6 +590,32 @@ func TestDownloadCopilot(t *testing.T) {
589590
})
590591
}
591592

593+
func TestRunCopilot_execFailureHint(t *testing.T) {
594+
ios, _, _, _ := iostreams.Test()
595+
opts := &CopilotOptions{
596+
IO: ios,
597+
CopilotArgs: []string{},
598+
}
599+
600+
origFind := findCopilotBinaryFunc
601+
findCopilotBinaryFunc = func() string {
602+
return "/usr/bin/copilot"
603+
}
604+
t.Cleanup(func() { findCopilotBinaryFunc = origFind })
605+
606+
execErr := fmt.Errorf("exec failed: something went wrong")
607+
origRun := runExternalCmdFunc
608+
runExternalCmdFunc = func(_ *exec.Cmd) error {
609+
return execErr
610+
}
611+
t.Cleanup(func() { runExternalCmdFunc = origRun })
612+
613+
err := runCopilot(opts)
614+
require.Error(t, err)
615+
require.ErrorIs(t, err, execErr)
616+
require.Contains(t, err.Error(), "try running `copilot` directly without `gh`.")
617+
}
618+
592619
func TestCopilotCommandIsSampledAt100(t *testing.T) {
593620
spy := &telemetry.CommandRecorderSpy{}
594621
factory := &cmdutil.Factory{}

0 commit comments

Comments
 (0)