Merge pull request #12845 from yuvrajangadsingh/refactor/deduplicate-scope-error-handling

refactor: deduplicate scope error handling between api/client.go and project queries
This commit is contained in:
Kynan Ware 2026-03-05 10:31:25 -07:00 committed by GitHub
commit c0cd4c13d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 4 additions and 66 deletions

View file

@ -203,7 +203,6 @@ func GenerateScopeErrorForGQL(gqlErr *ghAPI.GraphQLError) error {
}
if missing.Len() > 0 {
s := missing.ToSlice()
// TODO: this duplicates parts of generateScopesSuggestion
return fmt.Errorf(
"error: your authentication token is missing required scopes %v\n"+
"To request it, run: gh auth refresh -s %s",

View file

@ -5,13 +5,11 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/set"
"github.com/shurcooL/githubv4"
)
@ -1664,42 +1662,15 @@ func (c *Client) UnlinkProjectFromTeam(projectID string, teamID string) error {
}
func handleError(err error) error {
var gerr api.GraphQLError
if errors.As(err, &gerr) {
missing := set.NewStringSet()
for _, e := range gerr.Errors {
if e.Type != "INSUFFICIENT_SCOPES" {
continue
}
missing.AddValues(requiredScopesFromServerMessage(e.Message))
}
if missing.Len() > 0 {
s := missing.ToSlice()
// TODO: this duplicates parts of generateScopesSuggestion
return fmt.Errorf(
"error: your authentication token is missing required scopes %v\n"+
"To request it, run: gh auth refresh -s %s",
s,
strings.Join(s, ","))
var gqlErr api.GraphQLError
if errors.As(err, &gqlErr) {
if scopeErr := api.GenerateScopeErrorForGQL(gqlErr.GraphQLError); scopeErr != nil {
return scopeErr
}
}
return err
}
var scopesRE = regexp.MustCompile(`one of the following scopes: \[(.+?)]`)
func requiredScopesFromServerMessage(msg string) []string {
m := scopesRE.FindStringSubmatch(msg)
if m == nil {
return nil
}
var scopes []string
for _, mm := range strings.Split(m[1], ",") {
scopes = append(scopes, strings.Trim(mm, "' "))
}
return scopes
}
func projectFieldValueData(v FieldValueNodes) interface{} {
switch v.Type {
case "ProjectV2ItemFieldDateValue":

View file

@ -3,7 +3,6 @@ package queries
import (
"io"
"net/http"
"reflect"
"strings"
"testing"
@ -564,37 +563,6 @@ func TestProjectFields_NoLimit(t *testing.T) {
assert.Len(t, project.Fields.Nodes, 3)
}
func Test_requiredScopesFromServerMessage(t *testing.T) {
tests := []struct {
name string
msg string
want []string
}{
{
name: "no scopes",
msg: "SERVER OOPSIE",
want: []string(nil),
},
{
name: "one scope",
msg: "Your token has not been granted the required scopes to execute this query. The 'dataType' field requires one of the following scopes: ['read:project'], but your token has only been granted the: ['codespace', repo'] scopes. Please modify your token's scopes at: https://github.com/settings/tokens.",
want: []string{"read:project"},
},
{
name: "multiple scopes",
msg: "Your token has not been granted the required scopes to execute this query. The 'dataType' field requires one of the following scopes: ['read:project', 'read:discussion', 'codespace'], but your token has only been granted the: [repo'] scopes. Please modify your token's scopes at: https://github.com/settings/tokens.",
want: []string{"read:project", "read:discussion", "codespace"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := requiredScopesFromServerMessage(tt.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("requiredScopesFromServerMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewProject_nonTTY(t *testing.T) {
client := NewTestClient()
_, err := client.NewProject(false, &Owner{}, 0, false)