From 99516d64bae63193ff74727ff92d3c4e98294713 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Wed, 23 Jul 2025 15:29:32 -0400 Subject: [PATCH 01/12] Regenerate third-party licenses on trunk pushes Fixes #11270 This commit refactors the work done in #11047 of blocking pull requests for manual `third-party` license updates to having GitHub Actions automatically update it on pushes to `trunk`. This will allow maintainers to streamline Dependabot PR reviews while reducing contributor friction when changing dependencies. --- .github/workflows/lint.yml | 16 ------- .github/workflows/third-party-licenses.yml | 49 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/third-party-licenses.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5281a46d0..5afe6cd2f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,15 +7,11 @@ on: - "**.go" - go.mod - go.sum - - ".github/licenses.tmpl" - - "script/licenses*" pull_request: paths: - "**.go" - go.mod - go.sum - - ".github/licenses.tmpl" - - "script/licenses*" permissions: contents: read jobs: @@ -50,18 +46,6 @@ jobs: with: version: v2.1.6 - # actions/setup-go does not setup the installed toolchain to be preferred over the system install, - # which causes go-licenses to raise "Package ... does not have module info" errors. - # for more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 - # - # go-licenses has been pinned for automation use. - - name: Check licenses - run: | - export GOROOT=$(go env GOROOT) - export PATH=${GOROOT}/bin:$PATH - go install github.com/google/go-licenses@5348b744d0983d85713295ea08a20cca1654a45e - make licenses-check - # Discover vulnerabilities within Go standard libraries used to build GitHub CLI using govulncheck. govulncheck: runs-on: ubuntu-latest diff --git a/.github/workflows/third-party-licenses.yml b/.github/workflows/third-party-licenses.yml new file mode 100644 index 000000000..b9d29e9dc --- /dev/null +++ b/.github/workflows/third-party-licenses.yml @@ -0,0 +1,49 @@ +name: Third Party Licenses +on: + push: + branches: + - trunk + paths: + - go.mod + - go.sum + - ".github/licenses.tmpl" + - "script/licenses*" +jobs: + # This job is responsible for updating the third-party license reports and source code. + # It should be safe to cancel as the latest version of `go.mod` should be checked in. + regenerate-licenses: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + token: ${{ secrets.AUTOMATION_TOKEN }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Regenerate licenses + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + go install github.com/google/go-licenses@5348b744d0983d85713295ea08a20cca1654a45e + make licenses + git diff + + - name: Commit and push changes + run: | + if git diff --exit-code; then + echo "No third-party license changes to commit" + else + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add third-party third-party-licenses.*.md + git commit -m "Generate licenses - $GITHUB_SHA" + git pull + git push origin + fi From 6cce077a83015c49658270af51751d2a41d17df7 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Mon, 28 Jul 2025 11:38:50 +0100 Subject: [PATCH 02/12] docs(ci): delete obsolete comment Signed-off-by: Babak K. Shandiz --- .github/workflows/scripts/spam-detection/eval.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/scripts/spam-detection/eval.sh b/.github/workflows/scripts/spam-detection/eval.sh index 4bf8b08fe..2a0b93d4c 100755 --- a/.github/workflows/scripts/spam-detection/eval.sh +++ b/.github/workflows/scripts/spam-detection/eval.sh @@ -14,8 +14,4 @@ SPAM_DIR="$(dirname "$(realpath "$0")")" _system_prompt="$($SPAM_DIR/generate-sys-prompt.sh)" _final_prompt="$(_value="$_system_prompt" yq eval '.messages[0].content = strenv(_value)' $SPAM_DIR/eval-prompts.yml)" -# The following `gh models eval` command will fail after 20 requests due to rate limits. -# We are going to open up an issue in `github/gh-models` to address this. -# -# TODO: break up `eval-prompts.yml` file into smaller batches to avoid hitting the rate limit. gh models eval <(echo "$_final_prompt") From 8f20f0ab53bb40e3b5a9f6153f8fa18276bc1f9b Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Tue, 29 Jul 2025 18:19:22 -0400 Subject: [PATCH 03/12] Include org teams for PR reviewers This commit enhances the following scenarios to include organization teams as pull request reviewers: 1. Interactive `gh pr create` during additional metadata 2. Tab completing `gh pr create --reviewer` 3. Tab completing `gh pr edit --add-reviewer` 4. Tab completing `gh pr edit --remove-reviewer` Additionally, a new `gh pr create` test case for ensuring that teams show up within interactive prompts has been added. --- pkg/cmd/pr/create/create_test.go | 128 ++++++++++++++++++++++++++++++- pkg/cmd/pr/shared/completion.go | 5 +- pkg/cmd/pr/shared/survey.go | 1 + 3 files changed, 131 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 469428670..9af351830 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -1655,7 +1655,7 @@ func Test_createRun(t *testing.T) { expectedOut: "https://github.com/OWNER/REPO/pull/12\n", }, { - name: "if reviewer contains any team, fetch teams", + name: "fetch org teams non-interactively if reviewer contains any team", setup: func(opts *CreateOptions, t *testing.T) func() { opts.TitleProvided = true opts.BodyProvided = true @@ -1718,7 +1718,7 @@ func Test_createRun(t *testing.T) { expectedErrOut: "", }, { - name: "if reviewer does NOT contain any team, do NOT fetch teams", + name: "do not fetch org teams non-interactively if reviewer does not contain any team", setup: func(opts *CreateOptions, t *testing.T) func() { opts.TitleProvided = true opts.BodyProvided = true @@ -1773,6 +1773,130 @@ func Test_createRun(t *testing.T) { expectedOut: "https://github.com/OWNER/REPO/pull/12\n", expectedErrOut: "", }, + { + name: "fetch org teams interactively if reviewer metadata selected", + tty: true, + setup: func(opts *CreateOptions, t *testing.T) func() { + // In order to test additional metadata, title and body cannot be provided here. + opts.HeadBranch = "feature" + return func() {} + }, + cmdStubs: func(cs *run.CommandStubber) { + // Stub git commits for `initDefaultTitleBody` when initializing PR state. + cs.Register( + "git -c log.ShowSignature=false log --pretty=format:%H%x00%s%x00%b%x00 --cherry origin/master...feature", + 0, + "3a9b48085046d156c5acce8f3b3a0532cd706a4a\u0000first commit of pr\u0000first commit description\u0000\n", + ) + cs.Register(`git rev-parse --show-toplevel`, 0, "") + }, + promptStubs: func(pm *prompter.PrompterMock) { + firstConfirmSubmission := true + pm.InputFunc = func(message, defaultValue string) (string, error) { + switch message { + case "Title (required)": + return "TITLE", nil + default: + return "", fmt.Errorf("unexpected input prompt: %s", message) + } + } + pm.MarkdownEditorFunc = func(message, defaultValue string, allowEmpty bool) (string, error) { + switch message { + case "Body": + return "BODY", nil + default: + return "", fmt.Errorf("unexpected markdown editor prompt: %s", message) + } + } + pm.MultiSelectFunc = func(message string, defaults []string, options []string) ([]int, error) { + switch message { + case "What would you like to add?": + return prompter.IndexesFor(options, "Reviewers") + case "Reviewers": + return prompter.IndexesFor(options, "MonaLisa (Mona Display Name)", "OWNER/core") + default: + return nil, fmt.Errorf("unexpected multi-select prompt: %s", message) + } + } + pm.SelectFunc = func(message, defaultValue string, options []string) (int, error) { + switch message { + case "Where should we push the 'feature' branch?": + return 0, nil + case "What's next?": + if firstConfirmSubmission { + firstConfirmSubmission = false + return prompter.IndexFor(options, "Add metadata") + } + return prompter.IndexFor(options, "Submit") + default: + return 0, fmt.Errorf("unexpected select prompt: %s", message) + } + } + }, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query PullRequestTemplates\b`), + httpmock.StringResponse(`{ "data": { "repository": { "pullRequestTemplates": [] } } }`), + ) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID", "name": "" }, + { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "core", "id": "COREID" }, + { "slug": "robots", "id": "ROBOTID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { "data": { "createPullRequest": { "pullRequest": { + "id": "NEWPULLID", + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } } + `, + func(inputs map[string]interface{}) { + assert.Equal(t, "TITLE", inputs["title"]) + assert.Equal(t, "BODY", inputs["body"]) + if v, ok := inputs["assigneeIds"]; ok { + t.Errorf("did not expect assigneeIds: %v", v) + } + if v, ok := inputs["userIds"]; ok { + t.Errorf("did not expect userIds: %v", v) + } + })) + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreateRequestReviews\b`), + httpmock.GraphQLMutation(` + { "data": { "requestReviews": { + "clientMutationId": "" + } } } + `, func(inputs map[string]interface{}) { + assert.Equal(t, "NEWPULLID", inputs["pullRequestId"]) + assert.Equal(t, []interface{}{"COREID"}, inputs["teamIds"]) + assert.Equal(t, []interface{}{"MONAID"}, inputs["userIds"]) + assert.Equal(t, true, inputs["union"]) + })) + }, + expectedOut: "https://github.com/OWNER/REPO/pull/12\n", + expectedErrOut: "\nCreating pull request for feature into master in OWNER/REPO\n\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cmd/pr/shared/completion.go b/pkg/cmd/pr/shared/completion.go index c1296be71..9f68c0bda 100644 --- a/pkg/cmd/pr/shared/completion.go +++ b/pkg/cmd/pr/shared/completion.go @@ -14,7 +14,10 @@ import ( func RequestableReviewersForCompletion(httpClient *http.Client, repo ghrepo.Interface) ([]string, error) { client := api.NewClientFromHTTP(api.NewCachedHTTPClient(httpClient, time.Minute*2)) - metadata, err := api.RepoMetadata(client, repo, api.RepoMetadataInput{Reviewers: true}) + metadata, err := api.RepoMetadata(client, repo, api.RepoMetadataInput{ + Reviewers: true, + TeamReviewers: true, + }) if err != nil { return nil, err } diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index af1fa871a..4b66bb0fa 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -181,6 +181,7 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface // Retrieve and process data for survey prompts based on the extra fields selected metadataInput := api.RepoMetadataInput{ Reviewers: isChosen("Reviewers"), + TeamReviewers: isChosen("Reviewers"), Assignees: isChosen("Assignees"), ActorAssignees: isChosen("Assignees") && state.ActorAssignees, Labels: isChosen("Labels"), From c9bc185209fd8f2f6624f1e61af9827ef842bfb6 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Wed, 30 Jul 2025 09:16:44 -0400 Subject: [PATCH 04/12] Test `gh pr create --reviewer` tab completion --- pkg/cmd/pr/create/create_test.go | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 9af351830..eb39db0f9 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -2879,3 +2879,136 @@ func TestProjectsV1Deprecation(t *testing.T) { }) }) } + +func Test_requestableReviewersForCompletion(t *testing.T) { + tests := []struct { + name string + tty bool + expectedReviewers []string + httpStubs func(*httpmock.Registry, *testing.T) + }{ + { + name: "when users and teams are both available, both are listed", + expectedReviewers: []string{"MonaLisa\tMona Display Name", "OWNER/core", "OWNER/robots", "hubot"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID", "name": "" }, + { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "core", "id": "COREID" }, + { "slug": "robots", "id": "ROBOTID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + { + name: "when users are available but teams aren't, users are listed", + expectedReviewers: []string{"MonaLisa\tMona Display Name", "hubot"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID", "name": "" }, + { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + { + name: "when teams are available but users aren't, teams are listed", + expectedReviewers: []string{"OWNER/core", "OWNER/robots"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "core", "id": "COREID" }, + { "slug": "robots", "id": "ROBOTID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reg := &httpmock.Registry{} + defer reg.Verify(t) + if tt.httpStubs != nil { + tt.httpStubs(reg, t) + } + + ios, _, _, _ := iostreams.Test() + ios.SetStdoutTTY(tt.tty) + ios.SetStdinTTY(tt.tty) + ios.SetStderrTTY(tt.tty) + + opts := &CreateOptions{} + opts.IO = ios + opts.HttpClient = func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + } + opts.Remotes = func() (context.Remotes, error) { + return context.Remotes{ + { + Remote: &git.Remote{ + Name: "origin", + Resolved: "base", + }, + Repo: ghrepo.New("OWNER", "REPO"), + }, + }, nil + } + + reviewers, err := requestableReviewersForCompletion(opts) + require.NoError(t, err) + require.Equal(t, tt.expectedReviewers, reviewers) + }) + } +} From bbc3d02cb394c882c9d3bd21bbcf20db509ce973 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Wed, 30 Jul 2025 09:25:33 -0400 Subject: [PATCH 05/12] Refactor tab completion test This commit moves the `gh pr create` tab completion test closer to the logic rather than the commands that use it. This should ensure that any command or flag that lists reviewers will present teams and users as expected. --- pkg/cmd/pr/create/create_test.go | 133 --------------------------- pkg/cmd/pr/shared/completion_test.go | 120 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 133 deletions(-) create mode 100644 pkg/cmd/pr/shared/completion_test.go diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index eb39db0f9..9af351830 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -2879,136 +2879,3 @@ func TestProjectsV1Deprecation(t *testing.T) { }) }) } - -func Test_requestableReviewersForCompletion(t *testing.T) { - tests := []struct { - name string - tty bool - expectedReviewers []string - httpStubs func(*httpmock.Registry, *testing.T) - }{ - { - name: "when users and teams are both available, both are listed", - expectedReviewers: []string{"MonaLisa\tMona Display Name", "OWNER/core", "OWNER/robots", "hubot"}, - httpStubs: func(reg *httpmock.Registry, t *testing.T) { - reg.Register( - httpmock.GraphQL(`query UserCurrent\b`), - httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableUsers\b`), - httpmock.StringResponse(` - { "data": { "repository": { "assignableUsers": { - "nodes": [ - { "login": "hubot", "id": "HUBOTID", "name": "" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - reg.Register( - httpmock.GraphQL(`query OrganizationTeamList\b`), - httpmock.StringResponse(` - { "data": { "organization": { "teams": { - "nodes": [ - { "slug": "core", "id": "COREID" }, - { "slug": "robots", "id": "ROBOTID" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - }, - }, - { - name: "when users are available but teams aren't, users are listed", - expectedReviewers: []string{"MonaLisa\tMona Display Name", "hubot"}, - httpStubs: func(reg *httpmock.Registry, t *testing.T) { - reg.Register( - httpmock.GraphQL(`query UserCurrent\b`), - httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableUsers\b`), - httpmock.StringResponse(` - { "data": { "repository": { "assignableUsers": { - "nodes": [ - { "login": "hubot", "id": "HUBOTID", "name": "" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - reg.Register( - httpmock.GraphQL(`query OrganizationTeamList\b`), - httpmock.StringResponse(` - { "data": { "organization": { "teams": { - "nodes": [], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - }, - }, - { - name: "when teams are available but users aren't, teams are listed", - expectedReviewers: []string{"OWNER/core", "OWNER/robots"}, - httpStubs: func(reg *httpmock.Registry, t *testing.T) { - reg.Register( - httpmock.GraphQL(`query UserCurrent\b`), - httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableUsers\b`), - httpmock.StringResponse(` - { "data": { "repository": { "assignableUsers": { - "nodes": [], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - reg.Register( - httpmock.GraphQL(`query OrganizationTeamList\b`), - httpmock.StringResponse(` - { "data": { "organization": { "teams": { - "nodes": [ - { "slug": "core", "id": "COREID" }, - { "slug": "robots", "id": "ROBOTID" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - reg := &httpmock.Registry{} - defer reg.Verify(t) - if tt.httpStubs != nil { - tt.httpStubs(reg, t) - } - - ios, _, _, _ := iostreams.Test() - ios.SetStdoutTTY(tt.tty) - ios.SetStdinTTY(tt.tty) - ios.SetStderrTTY(tt.tty) - - opts := &CreateOptions{} - opts.IO = ios - opts.HttpClient = func() (*http.Client, error) { - return &http.Client{Transport: reg}, nil - } - opts.Remotes = func() (context.Remotes, error) { - return context.Remotes{ - { - Remote: &git.Remote{ - Name: "origin", - Resolved: "base", - }, - Repo: ghrepo.New("OWNER", "REPO"), - }, - }, nil - } - - reviewers, err := requestableReviewersForCompletion(opts) - require.NoError(t, err) - require.Equal(t, tt.expectedReviewers, reviewers) - }) - } -} diff --git a/pkg/cmd/pr/shared/completion_test.go b/pkg/cmd/pr/shared/completion_test.go new file mode 100644 index 000000000..ca7c3ffa7 --- /dev/null +++ b/pkg/cmd/pr/shared/completion_test.go @@ -0,0 +1,120 @@ +package shared + +import ( + "net/http" + "testing" + + "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/stretchr/testify/require" +) + +func TestRequestableReviewersForCompletion(t *testing.T) { + tests := []struct { + name string + expectedReviewers []string + httpStubs func(*httpmock.Registry, *testing.T) + }{ + { + name: "when users and teams are both available, both are listed", + expectedReviewers: []string{"MonaLisa\tMona Display Name", "OWNER/core", "OWNER/robots", "hubot"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID", "name": "" }, + { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "core", "id": "COREID" }, + { "slug": "robots", "id": "ROBOTID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + { + name: "when users are available but teams aren't, users are listed", + expectedReviewers: []string{"MonaLisa\tMona Display Name", "hubot"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID", "name": "" }, + { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + { + name: "when teams are available but users aren't, teams are listed", + expectedReviewers: []string{"OWNER/core", "OWNER/robots"}, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "core", "id": "COREID" }, + { "slug": "robots", "id": "ROBOTID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reg := &httpmock.Registry{} + defer reg.Verify(t) + if tt.httpStubs != nil { + tt.httpStubs(reg, t) + } + + reviewers, err := RequestableReviewersForCompletion(&http.Client{Transport: reg}, ghrepo.New("OWNER", "REPO")) + require.NoError(t, err) + require.Equal(t, tt.expectedReviewers, reviewers) + }) + } +} From 3d5675f5f71d8b32ec9e650d28caec01ff25a19f Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 31 Jul 2025 15:24:08 +0100 Subject: [PATCH 06/12] Improve spam detection evals (#11419) * ci: improve spam detection evals Signed-off-by: Babak K. Shandiz * ci: make test case names consistent Signed-off-by: Babak K. Shandiz * ci: remove ill-indented/redundant test case Signed-off-by: Babak K. Shandiz --------- Signed-off-by: Babak K. Shandiz --- .../scripts/spam-detection/eval-prompts.yml | 251 +++++++----------- 1 file changed, 100 insertions(+), 151 deletions(-) diff --git a/.github/workflows/scripts/spam-detection/eval-prompts.yml b/.github/workflows/scripts/spam-detection/eval-prompts.yml index be7efddfc..15c61ff76 100644 --- a/.github/workflows/scripts/spam-detection/eval-prompts.yml +++ b/.github/workflows/scripts/spam-detection/eval-prompts.yml @@ -10,126 +10,6 @@ evaluators: string: equals: "{{expected}}" testData: - - name: not-spam, staff issue - expected: PASS - input: | - - Automatically update third party licenses during Dependabot PRs - - - - ## Overview - - With `cli/cli` lint process erring if 3rd party license information is not updated in https://github.com/cli/cli/pull/11047, Dependabot PRs will require maintainers to manually run `make licenses`. - - Recently, @williammartin opened https://github.com/cli/cli/pull/11269 with the [`script/fix-dependabot-licenses.sh`](https://github.com/cli/cli/blob/26d70bfb7bcc0b41dbdd50bfc51f827f1a5ad4c4/script/fix-dependabot-licenses.sh) script for maintainers to run that will find all Dependabot PRs and attempt to fix them where the lint workflow failed. This script is a manual repair effort, however it is possible to [use a GitHub Actions workflow to run the `make license` script for Dependabot PRs](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions): - - > ```yaml - > name: Dependabot fetch metadata - > on: pull_request - > - > permissions: - > pull-requests: write - > issues: write - > - > jobs: - > dependabot: - > runs-on: ubuntu-latest - > if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'owner/my_repo' - > steps: - > - name: Dependabot metadata - > id: metadata - > uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 - > with: - > github-token: "${{ secrets.GITHUB_TOKEN }}" - > # The following properties are now available: - > # - steps.metadata.outputs.dependency-names - > # - steps.metadata.outputs.dependency-type - > # - steps.metadata.outputs.update-type - > ``` - - This issue is aimed at implementing GitHub Actions workflow changes that will automatically update `third-party` license source code and `third-party-*.md` reports, eliminating the need for maintainers to manually repair Dependabot PRs. - - > [!NOTE] - > To download the `script/fix-dependabot-licenses.sh` script, run the following command: - > ```shell - > curl -o fix-dependabot-licenses.sh https://raw.githubusercontent.com/cli/cli/26d70bfb7bcc0b41dbdd50bfc51f827f1a5ad4c4/script/fix-dependabot-licenses.sh - > ``` - > - > Or checkout the original PR: - > - > ```shell - > gh pr checkout https://github.com/cli/cli/pull/11269 - > ``` - - ## Expected outcomes - - - When Dependabot PRs are opened, automation attempts to regenerate and commit updated license information via `make licenses` - - When Dependabot PRs are updated, status checks pass without maintainer action outside of reviewing PR - - name: not-spam, template-based - spam: true - title: | - Incorrect check summary with v2.45 and v2.75 - body: | - ### Describe the bug - - I got below confusing reports with v2.45 CLI on Ubuntu 22.04: - - ```sh - $ gh pr status - - Relevant pull requests in micropython/micropython - ... - Created by you - #17660 tests/extmod: Close UDP timely. [yf13:pull-udp-close] - ✓ Checks passing - #17638 unix/make: Drop i686-linux-gnu path. [yf13:pull-drop-i686-linux-gnu] - × 1/94 checks failing - $ gh pr checks 17638 - All checks were successful - 0 cancelled, 0 failing, 48 successful, 0 skipped, and 0 pending checks - ``` - I downloaded latest v2.75 CLI but it is the same. - - Meanwhile. from browser UI it seems checks passed. - - ### Affected version - - Please run `gh version` and paste the output below. - - ``` - $ gh version - gh version 2.75.0 (2025-07-09) - https://github.com/cli/cli/releases/tag/v2.75.0 - ``` - - ### Steps to reproduce the behavior - - see above descriptions. - - ### Expected vs actual behavior - - A clear and concise description of what you expected to happen and what actually happened. - - ### Logs - - Paste the activity from your command line. Redact if needed. - - - - - - name: not-spam, short/focused - expected: PASS - input: | - - Include `isImmutable` in `release list` - - - - Update the list of available JSON fields in the `release list` command to include `isImmutable` flag. - - This boolean flag indicates whether a particular release has been marked as immutable. - - name: spam, two words expected: FAIL input: | @@ -295,34 +175,6 @@ testData: Add any other context like screenshots or mockups are helpful, if applicable. - - name: 'spam, legit but too general #10368 (https://github.com/cli/cli/issues/10368)' - expected: FAIL - input: |- - - Instructions in install_linux.md do not result in installation - - - - ### Describe the bug - - Bug: the instructions meant to install gh instead don't install gh. - - ### Affected version - - Latest - - ### Steps to reproduce the behavior - - Follow instructions in install_linux.md - - ### Expected vs actual behavior - - Expect: gh is installed and can be used. - - ### Logs - - A bunch of errors - - name: 'spam, #11304 (https://github.com/cli/cli/issues/11304)' expected: FAIL input: |- @@ -820,6 +672,106 @@ testData: - name: 'spam, #9928 (https://github.com/cli/cli/issues/9928)' expected: FAIL input: "\nNote that an earlier version of the instructions used the location `/usr/share/keyrings` instead of `/etc/apt/keyrings` in the `sources.list.d` file, so I had to update that to make it work with the above update instructions, and remove the old keyring file from `/usr/share/keyrings`.\n\n\n\n Note that an earlier version of the instructions used the location `/usr/share/keyrings` instead of `/etc/apt/keyrings` in the `sources.list.d` file, so I had to update that to make it work with the above update instructions, and remove the old keyring file from `/usr/share/keyrings`.\r\n\r\nAlternatively, one could of course download the updated key to `/usr/share/keyrings`, but we don't really want to pollute `/usr` with non-packaged files!\r\n\r\n_Originally posted by @rrthomas in https://github.com/cli/cli/issues/9569#issuecomment-2333981674_\r\n \n" + - name: 'spam, #10075 (https://github.com/cli/cli/issues/10075)' + expected: FAIL + input: "\nRHEL 9 installation update\n\n\n\n### Describe the bug\r\n\r\nsteps to install on RHEL9 \r\n\r\n### Steps to reproduce the behavior\r\n\r\n\r\n### Expected vs actual behavior\r\n\r\n```\r\nsudo dnf install dnf-plugins-core.noarch\r\nsudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo\r\nsudo dnf install gh --repo gh-cli\r\n```\n" + - name: not spam, staff issue + expected: PASS + input: | + + Automatically update third party licenses during Dependabot PRs + + + + ## Overview + + With `cli/cli` lint process erring if 3rd party license information is not updated in https://github.com/cli/cli/pull/11047, Dependabot PRs will require maintainers to manually run `make licenses`. + + Recently, @williammartin opened https://github.com/cli/cli/pull/11269 with the [`script/fix-dependabot-licenses.sh`](https://github.com/cli/cli/blob/26d70bfb7bcc0b41dbdd50bfc51f827f1a5ad4c4/script/fix-dependabot-licenses.sh) script for maintainers to run that will find all Dependabot PRs and attempt to fix them where the lint workflow failed. This script is a manual repair effort, however it is possible to [use a GitHub Actions workflow to run the `make license` script for Dependabot PRs](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions): + + > ```yaml + > name: Dependabot fetch metadata + > on: pull_request + > + > permissions: + > pull-requests: write + > issues: write + > + > jobs: + > dependabot: + > runs-on: ubuntu-latest + > if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'owner/my_repo' + > steps: + > - name: Dependabot metadata + > id: metadata + > uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 + > with: + > github-token: "${{ secrets.GITHUB_TOKEN }}" + > # The following properties are now available: + > # - steps.metadata.outputs.dependency-names + > # - steps.metadata.outputs.dependency-type + > # - steps.metadata.outputs.update-type + > ``` + + This issue is aimed at implementing GitHub Actions workflow changes that will automatically update `third-party` license source code and `third-party-*.md` reports, eliminating the need for maintainers to manually repair Dependabot PRs. + + > [!NOTE] + > To download the `script/fix-dependabot-licenses.sh` script, run the following command: + > ```shell + > curl -o fix-dependabot-licenses.sh https://raw.githubusercontent.com/cli/cli/26d70bfb7bcc0b41dbdd50bfc51f827f1a5ad4c4/script/fix-dependabot-licenses.sh + > ``` + > + > Or checkout the original PR: + > + > ```shell + > gh pr checkout https://github.com/cli/cli/pull/11269 + > ``` + + ## Expected outcomes + + - When Dependabot PRs are opened, automation attempts to regenerate and commit updated license information via `make licenses` + - When Dependabot PRs are updated, status checks pass without maintainer action outside of reviewing PR + + - name: not spam, short/focused + expected: PASS + input: | + + Include `isImmutable` in `release list` + + + + Update the list of available JSON fields in the `release list` command to include `isImmutable` flag. + + This boolean flag indicates whether a particular release has been marked as immutable. + + - name: 'not spam, legit but too general #10368 (https://github.com/cli/cli/issues/10368)' + expected: PASS + input: |- + + Instructions in install_linux.md do not result in installation + + + + ### Describe the bug + + Bug: the instructions meant to install gh instead don't install gh. + + ### Affected version + + Latest + + ### Steps to reproduce the behavior + + Follow instructions in install_linux.md + + ### Expected vs actual behavior + + Expect: gh is installed and can be used. + + ### Logs + + A bunch of errors + - name: 'not spam, #11277 (https://github.com/cli/cli/issues/11277)' expected: PASS input: |- @@ -4482,9 +4434,6 @@ testData: - name: 'not spam, #10076 (https://github.com/cli/cli/issues/10076)' expected: PASS input: "\n`gh run list` does not work with organization ruleset required workflows\n\n\n\n### Describe the bug\r\n\r\nSimilar bug mentioned https://github.com/cli/cli/issues/3437, but gh run view or list all return a 404. The URL returned seems right based on REST api docs but not getting any response. When comparing the ID `gh run list` doesn't seem to be correct based on the ids from `gh workflow list`\r\n\r\n**gh cli version:** `gh version 2.63.2 (2024-12-05)`\r\n**ghe version:** `3.13.4`\r\n\r\n### Steps to reproduce the behavior\r\n\r\n1. Complete login to the enterprise server with Github CLI\r\n2. Go to a repository directory that uses that server as a remote\r\n3. Run gh run list returns 404\r\n\r\n### Expected vs actual behavior\r\n\r\nThe gh run list prints out list of workflow runs for the repo to choose from\r\n\r\n### Logs\r\n\r\n```bash\r\n[git remote -v]\r\n[git config --get-regexp ^remote\\..*\\.gh-resolved$]\r\n* Request at 2024-12-13 00:23:19.723417 -0600 CST m=+0.101249251\r\n* Request to https://{SERVER_URL}/api/graphql\r\n* Request took 281.385ms\r\n⣾* Request at 2024-12-13 00:23:20.040818 -0600 CST m=+0.418510918\r\n* Request to https://{SERVER_URL}/api/v3/repos/{ORG}/{REPO}/actions/runs?per_page=20&exclude_pull_requests=true\r\n⢿* Request took 421.362291ms\r\n⡿* Request at 2024-12-13 00:23:20.534045 -0600 CST m=+0.911535293\r\n* Request to https://{SERVER_URL}/api/v3/repos/{ORG}/{REPO}/actions/workflows?per_page=100&page=1\r\n⣟* Request took 105.218541ms\r\n* Request at 2024-12-13 00:23:20.700194 -0600 CST m=+1.077616418\r\n* Request to https://{SERVER_URL}/api/v3/repos/{ORG}/{REPO}/actions/workflows/63737\r\n⣯* Request took 121.476458ms\r\nfailed to get runs: HTTP 404: Not Found (https://{SERVER_URL}/api/v3/repos/{ORG}/{REPO}/actions/workflows/63737)\r\n```\r\n" - - name: 'not spam, #10075 (https://github.com/cli/cli/issues/10075)' - expected: PASS - input: "\nRHEL 9 installation update\n\n\n\n### Describe the bug\r\n\r\nsteps to install on RHEL9 \r\n\r\n### Steps to reproduce the behavior\r\n\r\n\r\n### Expected vs actual behavior\r\n\r\n```\r\nsudo dnf install dnf-plugins-core.noarch\r\nsudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo\r\nsudo dnf install gh --repo gh-cli\r\n```\n" - name: 'not spam, #10073 (https://github.com/cli/cli/issues/10073)' expected: PASS input: "\n`gh gist delete` does not prompt for a gist to delete or prompt for confirmation before deletion\n\n\n\n### Describe the bug\n\n- `gh gist delete` doesn't prompt for a gist to delete. This seems like it might be an oversight when compared to the behavior of other `gh gist` and `gh delete` operations.\n- `gh gist delete` should prompt for a gist to delete and confirm the selection to delete.\n- `gh gist delete` also does not currently support `--yes` for non-interactive confirmation - perhaps it should? \n\n### Steps to reproduce the behavior\n\n```\ngh gist delete\n```\n### Expected vs actual behavior\n\n**Expected**\n\n```\n❯ gh gist delete\n? Select a gist to delete [Use arrows to move, type to filter]\n> test.md test gist about 4 days ago\n draft.md about 2 months ago\n? Are you sure you want to delete gist test.md (Y/n)\n```\n\n**Actual**\n```\ngh gist delete\n❯ gh gist delete\ncannot delete: gist argument required\n\nUsage: gh gist delete { | } [flags]\n```\n\n### Notes\n\nDiscovered in #10042 \n" From f1996cd57165c72bba6f5534ffd9f31c6b6ea12a Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 31 Jul 2025 15:29:54 +0100 Subject: [PATCH 07/12] ci: anchor regexp for `help wanted` label Signed-off-by: Babak K. Shandiz --- .github/workflows/scripts/check-help-wanted.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/check-help-wanted.sh b/.github/workflows/scripts/check-help-wanted.sh index 75462ddb9..dd58586dd 100755 --- a/.github/workflows/scripts/check-help-wanted.sh +++ b/.github/workflows/scripts/check-help-wanted.sh @@ -56,7 +56,7 @@ for issue_num in $CLOSING_ISSUES; do fi # Check if 'help wanted' label exists - if ! echo "$LABELS" | grep -q "help wanted"; then + if ! echo "$LABELS" | grep -qE '^help wanted$'; then ISSUES_WITHOUT_HELP_WANTED+=("$issue_num") echo "Issue #$issue_num does not have 'help wanted' label" else From be67a350b88a72953cdf55955933abf17f54ea56 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 31 Jul 2025 15:34:06 +0100 Subject: [PATCH 08/12] ci: use `help wanted` label link in comment Signed-off-by: Babak K. Shandiz --- .github/workflows/scripts/check-help-wanted.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/check-help-wanted.sh b/.github/workflows/scripts/check-help-wanted.sh index dd58586dd..d713be144 100755 --- a/.github/workflows/scripts/check-help-wanted.sh +++ b/.github/workflows/scripts/check-help-wanted.sh @@ -78,7 +78,7 @@ if [ ${#ISSUES_WITHOUT_HELP_WANTED[@]} -gt 0 ]; then gh pr comment "$PR_URL" --body-file - < Date: Fri, 1 Aug 2025 15:36:55 -0400 Subject: [PATCH 09/12] Update permissions and events for workflow This commit makes a few notable changes: 1. Use the GitHub Actions automatic token for committing changes 2. Include workflow file in paths to trigger workflow 3. Checkout the default branch explicitly --- .github/workflows/third-party-licenses.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/third-party-licenses.yml b/.github/workflows/third-party-licenses.yml index b9d29e9dc..3cdc5858e 100644 --- a/.github/workflows/third-party-licenses.yml +++ b/.github/workflows/third-party-licenses.yml @@ -4,10 +4,11 @@ on: branches: - trunk paths: + - .github/licenses.tmpl + - .github/workflows/third-party-licenses.yml - go.mod - go.sum - - ".github/licenses.tmpl" - - "script/licenses*" + - script/licenses* jobs: # This job is responsible for updating the third-party license reports and source code. # It should be safe to cancel as the latest version of `go.mod` should be checked in. @@ -16,11 +17,13 @@ jobs: concurrency: group: ${{ github.workflow }} cancel-in-progress: true + permissions: + contents: write steps: - name: Check out code uses: actions/checkout@v4 with: - token: ${{ secrets.AUTOMATION_TOKEN }} + ref: trunk - name: Set up Go uses: actions/setup-go@v5 From 60fdb7ec2bd546040e0864e57bfbc0a4b2c4bdb8 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Fri, 1 Aug 2025 16:50:55 -0400 Subject: [PATCH 10/12] Update spam detection to comment on and close issue Fixes #11408 These changes enhance the GitHub CLI spam detection logic to automatically comment on and close suspected spam based on the past weeks of usage. Additionally, there were a few minor enhancements to the script, allowing it to be executed from anywhere rather than the root of the local repository. --- .../scripts/spam-detection/process-issue.sh | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scripts/spam-detection/process-issue.sh b/.github/workflows/scripts/spam-detection/process-issue.sh index f65cb47e8..d9aff160f 100755 --- a/.github/workflows/scripts/spam-detection/process-issue.sh +++ b/.github/workflows/scripts/spam-detection/process-issue.sh @@ -9,16 +9,17 @@ set -euo pipefail +# Determine absolute path to script directory based on where it is called from. +# This allows the script to be run from any directory. +SPAM_DIR="$(dirname "$(realpath "$0")")" + _issue_url="$1" if [[ -z "$_issue_url" ]]; then echo "error: issue URL is empty" >&2 exit 1 fi -_suspected_spam_label="suspected-spam" -_check_issue_script=".github/workflows/scripts/spam-detection/check-issue.sh" - -_result="$($_check_issue_script "$_issue_url")" +_result="$("$SPAM_DIR/check-issue.sh" "$_issue_url")" if [[ "$_result" == "PASS" ]]; then echo "detected as not-spam: $_issue_url" @@ -27,6 +28,17 @@ fi echo "detected as spam: $_issue_url" -gh issue edit --add-label "$_suspected_spam_label" "$_issue_url" +cat << EOF | gh issue comment "$_issue_url" --body-file - +Thank you for taking the time to create this issue. + +We've automatically reviewed this issue and suspect it as potentially inauthentic or spam-like content. As a result, we're closing this issue. + +**If this was closed by mistake**, please don't hesitate to reach out to us by commenting on this issue with additional context. + +We appreciate your understanding and apologize if this action was taken in error. Our automated systems help us manage the large volume of issues we receive, but we know they're not perfect. +EOF + +gh issue edit --add-label "suspected-spam" --add-label "invalid" "$_issue_url" +gh issue close "$_issue_url" echo "issue labelled as suspected spam" From 1e69d8a1a0676b4dbf9b2aba67fed7a53054cd0b Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Mon, 4 Aug 2025 08:30:04 -0400 Subject: [PATCH 11/12] Update .github/workflows/scripts/spam-detection/process-issue.sh Co-authored-by: Babak K. Shandiz --- .github/workflows/scripts/spam-detection/process-issue.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/spam-detection/process-issue.sh b/.github/workflows/scripts/spam-detection/process-issue.sh index d9aff160f..10e5a528d 100755 --- a/.github/workflows/scripts/spam-detection/process-issue.sh +++ b/.github/workflows/scripts/spam-detection/process-issue.sh @@ -39,6 +39,6 @@ We appreciate your understanding and apologize if this action was taken in error EOF gh issue edit --add-label "suspected-spam" --add-label "invalid" "$_issue_url" -gh issue close "$_issue_url" +gh issue close --reason 'not planned' "$_issue_url" echo "issue labelled as suspected spam" From ccc1b4f8c7d8596fcb061d69322515e643cadb08 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Mon, 4 Aug 2025 08:30:16 -0400 Subject: [PATCH 12/12] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/scripts/spam-detection/process-issue.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/spam-detection/process-issue.sh b/.github/workflows/scripts/spam-detection/process-issue.sh index 10e5a528d..ed8254356 100755 --- a/.github/workflows/scripts/spam-detection/process-issue.sh +++ b/.github/workflows/scripts/spam-detection/process-issue.sh @@ -41,4 +41,4 @@ EOF gh issue edit --add-label "suspected-spam" --add-label "invalid" "$_issue_url" gh issue close --reason 'not planned' "$_issue_url" -echo "issue labelled as suspected spam" +echo "issue processed as suspected spam: commented, closed, and labeled"