Merge pull request #13393 from cli/babakks/improve-gh-copilot-error

fix(copilot): hint to run copilot directly when exec fails
This commit is contained in:
Babak K. Shandiz 2026-05-12 14:14:02 +01:00 committed by GitHub
commit fe996d33ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 3 deletions

View file

@ -142,8 +142,9 @@ func runCopilot(opts *CopilotOptions) error {
return nil
}
copilotPath := findCopilotBinary()
if copilotPath == "" {
copilotPath := findCopilotBinaryFunc()
foundInPath := copilotPath != ""
if !foundInPath {
if opts.IO.CanPrompt() {
confirmed, err := opts.Prompter.Confirm("GitHub Copilot CLI is not installed. Would you like to install it?", true)
if err != nil {
@ -175,12 +176,18 @@ func runCopilot(opts *CopilotOptions) error {
externalCmd.Stderr = opts.IO.ErrOut
externalCmd.Env = append(os.Environ(), "COPILOT_GH=true")
if err := externalCmd.Run(); err != nil {
if err := runExternalCmdFunc(externalCmd); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// We terminate with os.Exit here, preserving the exit code from Copilot CLI,
// and also preventing stdio writes by callers up the stack.
os.Exit(exitErr.ExitCode())
}
if foundInPath {
// We found a `copilot` binary but exec failed, possibly due to
// unusual characters in the path (see https://github.com/cli/cli/issues/13106).
// Suggest running copilot directly as a workaround.
return fmt.Errorf("%w\nFailed to run '%s', try running `copilot` directly without `gh`.", err, copilotPath)
}
return err
}
return nil
@ -200,6 +207,14 @@ func copilotBinaryPath() string {
return filepath.Join(copilotInstallDir(), binaryName)
}
var runExternalCmdFunc = runExternalCmd
func runExternalCmd(cmd *exec.Cmd) error {
return cmd.Run()
}
var findCopilotBinaryFunc = findCopilotBinary
// findCopilotBinary returns the path to the Copilot CLI binary, if installed,
// with the following order of precedence:
// 1. `copilot` in the PATH

View file

@ -10,6 +10,7 @@ import (
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
@ -589,6 +590,32 @@ func TestDownloadCopilot(t *testing.T) {
})
}
func TestRunCopilot_execFailureHint(t *testing.T) {
ios, _, _, _ := iostreams.Test()
opts := &CopilotOptions{
IO: ios,
CopilotArgs: []string{},
}
origFind := findCopilotBinaryFunc
findCopilotBinaryFunc = func() string {
return "/usr/bin/copilot"
}
t.Cleanup(func() { findCopilotBinaryFunc = origFind })
execErr := fmt.Errorf("exec failed: something went wrong")
origRun := runExternalCmdFunc
runExternalCmdFunc = func(_ *exec.Cmd) error {
return execErr
}
t.Cleanup(func() { runExternalCmdFunc = origRun })
err := runCopilot(opts)
require.Error(t, err)
require.ErrorIs(t, err, execErr)
require.Contains(t, err.Error(), "try running `copilot` directly without `gh`.")
}
func TestCopilotCommandIsSampledAt100(t *testing.T) {
spy := &telemetry.CommandRecorderSpy{}
factory := &cmdutil.Factory{}