From bff468bafe1d7144d23aaf7ff25191361f16f9e2 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:25:32 -0600 Subject: [PATCH] fix(pr create): wire up @copilot assignee replacement and [bot] suffix Two bugs introduced in #13009 found during acceptance testing: 1. `pr create --assignee @copilot` sent the literal `@copilot` to the API because NewIssueState only ran MeReplacer on assignees, not CopilotReplacer. Fixed by switching to SpecialAssigneeReplacer (which handles both @me and @copilot) like issue create already does. 2. The replaceActorsForAssignable mutation requires the [bot] suffix on bot actor logins (e.g. `copilot-swe-agent[bot]`), unlike requestReviewsByLogin which has a separate botLogins field. Added the suffix in ReplaceActorsForAssignableByLogin for CopilotAssigneeLogin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/queries_pr.go | 6 ++++++ pkg/cmd/issue/create/create_test.go | 4 ++-- pkg/cmd/pr/create/create.go | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 073ef6886..29342521f 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -599,6 +599,12 @@ func ReplaceActorsForAssignableByLogin(client *Client, repo ghrepo.Interface, as actorLogins := make([]githubv4.String, len(logins)) for i, l := range logins { + // The replaceActorsForAssignable mutation requires the [bot] suffix + // for bot actor logins (e.g. "copilot-swe-agent[bot]"), unlike + // requestReviewsByLogin which has a separate botLogins field. + if l == CopilotAssigneeLogin { + l = l + "[bot]" + } actorLogins[i] = githubv4.String(l) } diff --git a/pkg/cmd/issue/create/create_test.go b/pkg/cmd/issue/create/create_test.go index 57de4c9b5..bf8315a9a 100644 --- a/pkg/cmd/issue/create/create_test.go +++ b/pkg/cmd/issue/create/create_test.go @@ -548,7 +548,7 @@ func Test_createRun(t *testing.T) { { "data": { "replaceActorsForAssignable": { "__typename": "" } } } `, func(inputs map[string]interface{}) { assert.Equal(t, "ISSUEID", inputs["assignableId"]) - assert.Equal(t, []interface{}{"copilot-swe-agent", "MonaLisa"}, inputs["actorLogins"]) + assert.Equal(t, []interface{}{"copilot-swe-agent[bot]", "MonaLisa"}, inputs["actorLogins"]) })) }, wantsStdout: "https://github.com/OWNER/REPO/issues/12\n", @@ -1161,7 +1161,7 @@ func TestIssueCreate_AtCopilotAssignee(t *testing.T) { { "data": { "replaceActorsForAssignable": { "__typename": "" } } } `, func(inputs map[string]interface{}) { assert.Equal(t, "NEWISSUEID", inputs["assignableId"]) - assert.Equal(t, []interface{}{"copilot-swe-agent"}, inputs["actorLogins"]) + assert.Equal(t, []interface{}{"copilot-swe-agent[bot]"}, inputs["actorLogins"]) })) output, err := runCommand(http, true, `-a @copilot -t hello -b "cash rules everything around me"`, nil) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 37f658379..1a3eba7d8 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -424,7 +424,7 @@ func createRun(opts *CreateOptions) error { assigneeSearchFunc = shared.RepoAssigneeSearchFunc(client, ctx.PRRefs.BaseRepo()) } - state, err := NewIssueState(*ctx, *opts) + state, err := NewIssueState(*ctx, *opts, issueFeatures.ApiActorsSupported) if err != nil { return err } @@ -672,14 +672,14 @@ func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, u return nil } -func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadataState, error) { +func NewIssueState(ctx CreateContext, opts CreateOptions, apiActorsSupported bool) (*shared.IssueMetadataState, error) { var milestoneTitles []string if opts.Milestone != "" { milestoneTitles = []string{opts.Milestone} } - meReplacer := shared.NewMeReplacer(ctx.Client, ctx.PRRefs.BaseRepo().RepoHost()) - assignees, err := meReplacer.ReplaceSlice(opts.Assignees) + assigneeReplacer := shared.NewSpecialAssigneeReplacer(ctx.Client, ctx.PRRefs.BaseRepo().RepoHost(), apiActorsSupported, !opts.WebMode) + assignees, err := assigneeReplacer.ReplaceSlice(opts.Assignees) if err != nil { return nil, err }