Skip to content

Use login-based assignee mutation on github.com#13009

Merged
BagToad merged 7 commits intotrunkfrom
fix/pr-create-assignee-metadata-13000
Mar 25, 2026
Merged

Use login-based assignee mutation on github.com#13009
BagToad merged 7 commits intotrunkfrom
fix/pr-create-assignee-metadata-13000

Conversation

@BagToad
Copy link
Member

@BagToad BagToad commented Mar 24, 2026

Fixes #13000

Description

gh pr create -a @me fails with could not assign user: 'xx' not found when the user interactively adds metadata (reviewers, labels, etc.) during the PR creation flow, because the assignee login cannot be resolved to a node ID from the cached metadata that only contains the interactively selected fields. This has been broken since the actor assignee work shipped a few weeks ago.

While investigating, we found that all assignee flows on github.com were still going through an unnecessary bulk fetch and ID resolution step, even though the API already supports assigning by login directly. This PR fixes the original bug and migrates all assignee paths to use the login-based approach.

To Fix

This PR migrates all github.com assignee flows to pass logins directly to the mutation, and wires up the multi-select with search UX. This experience avoids bulk fetching of IDs. GHES retains the legacy ID-based path unchanged.

Before

Path Bulk fetch? ID resolution?
gh pr create -a ❌ Yes ❌ Yes
gh pr create -a + interactive metadata ❌ Yes (does not work) ❌ Yes (does not work)
gh pr create interactive assignees ❌ Yes (static list) ❌ Yes
gh issue create -a ✅ No ✅ No
gh issue create interactive assignees ❌ Yes (static list) ❌ Yes
gh pr edit interactive ✅ No ⚠️ Yes
gh pr edit --add-assignee ❌ Yes ❌ Yes
gh issue edit interactive ❌ Yes (static list) ❌ Yes
gh issue edit --add-assignee ❌ Yes ❌ Yes

After

Path Bulk fetch? ID resolution?
gh pr create -a ✅ No ✅ No
gh pr create -a + interactive metadata ✅ No ✅ No
gh pr create interactive assignees ✅ No (search) ✅ No
gh issue create -a ✅ No ✅ No
gh issue create interactive assignees ✅ No (search) ✅ No
gh pr edit interactive ✅ No (search) ✅ No
gh pr edit --add-assignee ✅ No ✅ No
gh issue edit interactive ✅ No (search) ✅ No
gh issue edit --add-assignee ✅ No ✅ No

Key Changes

  • Fix the missing state.ActorAssignees = true in pr create that caused the original bug
  • On github.com, assignee logins now go straight to the replaceActorsForAssignable mutation via its actorLogins field. No more fetching all assignable actors just to resolve a login to a node ID
  • The --add-assignee and --remove-assignee flag paths for both pr edit and issue edit skip the bulk fetch entirely on github.com. A new AssigneeLogins() method computes the final set from logins directly
  • All interactive assignee selection now uses MultiSelectWithSearch on github.com:
    • pr create and issue create via MetadataSurvey (new assigneeSearchFunc parameter, backed by new SearchRepoAssignableActors repo-level API)
    • issue edit wired up with AssigneeSearchFunc (previously missing, was using static list)
    • pr edit already had search, now simplified (actor accumulation hack removed)
  • AssigneeSearchFunc extracted to shared location, actorsToSearchResult helper shared by both repo-level and node-level search functions
  • Fixed a bug where Editable.Clone() was silently dropping AssigneeSearchFunc and ReviewerSearchFunc

Reviewer Notes

  • The replaceActorsForAssignable mutation has always accepted actorLogins alongside actorIds on github.com. We just never used it until now
  • AssigneeIds() on Editable is now only called on GHES. On github.com, AssigneeLogins() is used instead
  • MetadataSurvey now accepts an assigneeSearchFunc parameter alongside the existing reviewerSearchFunc
  • Create flows use SearchRepoAssignableActors (repo-level, no issue/PR ID needed), edit flows use SuggestedAssignableActors (node-level)
  • GHES flows are completely unchanged, they still use RepositoryAssignableUsers + assigneeIds

Acceptance & Regression Testing

16 scenarios tested across github.com and GHES (3.20), all passing: Acceptance test results

Covers pr create, issue create, pr edit (flags + interactive), issue edit (flags + interactive), multi-issue edit, remove-all-assignees, and zero-bulk-fetch verification on both hosts.

The full automated acceptance test suite (go test -tags=acceptance ./acceptance) was also run locally against github.com with all relevant tests passing.

BagToad and others added 5 commits March 23, 2026 15:21
When ActorAssignees is true (github.com), pass assignee logins directly
to the ReplaceActorsForAssignable mutation instead of resolving logins
to node IDs. This eliminates the need to bulk fetch all assignable
users/actors and fixes a bug where providing assignees via CLI flag
and then interactively adding metadata would fail with 'not found'
because the cached MetadataResult had no assignee data.

Changes:
- Set state.ActorAssignees = true in pr create (was missing)
- AddMetadataToIssueParams: pass assigneeLogins when ActorAssignees
  is true, skip fetch and ID resolution entirely
- CreatePullRequest/IssueCreate: call ReplaceActorsForAssignableByLogin
  after creation to assign via logins
- Consolidate replaceActorsForAssignable mutation into api/ package
  (ReplaceActorsForAssignableByLogin + ReplaceActorsForAssignableByID)
- Remove duplicate replaceActorAssigneesForEditable from editable_http.go
- Add TODO replaceActorsByLoginCleanup markers on edit paths

Fixes #13000

Co-authored-by: Copilot <[email protected]>
…flag flows

When ActorAssignees is true (github.com), the --add-assignee and
--remove-assignee flag flows now pass logins directly to
ReplaceActorsForAssignableByLogin instead of bulk fetching all
assignable actors and resolving logins to node IDs.

Changes:
- New AssigneeLogins() method on Editable that computes the final
  login set (defaults + add - remove) without ID resolution
- UpdateIssue: call AssigneeLogins + ByLogin when ActorAssignees is true
- EditableOptionsFetch: skip assignee bulk fetch for flag flows on
  github.com (only fetch on GHES where ID resolution is needed)

Co-authored-by: Copilot <[email protected]>
The assigneeSearchFunc previously accumulated actors into
editable.Metadata.AssignableActors so that MembersToIDs could
later resolve logins to node IDs. Now that the edit flow uses
AssigneeLogins + ReplaceActorsForAssignableByLogin on github.com,
this accumulation is no longer needed.

Co-authored-by: Copilot <[email protected]>
Add AssigneeSearchFunc to gh issue edit interactive flow, matching
the pattern already used in gh pr edit. This eliminates the bulk
RepositoryAssignableActors fetch for interactive assignee selection,
using dynamic SuggestedAssignableActors search instead.

Also clean up pr edit assigneeSearchFunc signature to remove the
unused editable parameter (no longer needed after removing the
actor accumulation hack).

Co-authored-by: Copilot <[email protected]>
- Fix tests: assert logins (not display names) in actorLogins
- Remove dead ReplaceActorsForAssignableByID (no callers)
- Extract shared AssigneeSearchFunc to pkg/cmd/pr/shared/editable.go
- Remove duplicate assigneeSearchFunc from pr/edit and issue/edit

Co-authored-by: Copilot <[email protected]>
@BagToad BagToad marked this pull request as ready for review March 24, 2026 00:34
@BagToad BagToad requested a review from a team as a code owner March 24, 2026 00:34
@BagToad BagToad changed the title Use login-based assignee mutation on github.com WIP: Use login-based assignee mutation on github.com Mar 24, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes gh pr create -a @me failing when interactive metadata is added during PR creation by switching github.com assignee handling to a login-based mutation path (skipping node ID resolution and bulk assignable-actor fetches), while keeping GHES on the legacy ID-based flow.

Changes:

  • Enable actor-based assignee handling in pr create (state.ActorAssignees = true) and pass assignee logins through to mutations on github.com.
  • Add a shared ReplaceActorsForAssignableByLogin helper and use it for PR/issue creation and edit flows on github.com.
  • Rework edit flows to avoid bulk assignee metadata fetches on github.com and share an assignee search function; fix Editable.Clone() to preserve search funcs.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pkg/cmd/pr/shared/params.go Avoid assignee ID resolution on github.com; pass assigneeLogins instead.
pkg/cmd/pr/shared/editable_http.go Use the shared login-based replace-actors mutation for actor assignees; remove local mutation helper.
pkg/cmd/pr/shared/editable.go Add AssigneeLogins(), preserve search funcs in Clone(), reduce assignee metadata fetching, and extract shared AssigneeSearchFunc.
pkg/cmd/pr/edit/edit.go Switch to the shared assignee search function.
pkg/cmd/pr/edit/edit_test.go Update expectations to assert actorLogins usage and no assignee metadata prefetch.
pkg/cmd/pr/create/create.go Ensure actor assignees are enabled when ActorIsAssignable is available.
pkg/cmd/pr/create/create_test.go Expect assignee assignment via ReplaceActorsForAssignable with actorLogins.
pkg/cmd/issue/edit/edit.go Wire the shared assignee search function for interactive actor-assignee selection.
pkg/cmd/issue/edit/edit_test.go Remove bulk assignable-actors fetch stubs; assert actorLogins usage.
pkg/cmd/issue/create/create_test.go Expect post-create actor assignment via ReplaceActorsForAssignable with actorLogins (and include created issue id).
api/queries_pr.go Assign assignees post-create via login-based replace-actors mutation; add ReplaceActorsForAssignableByLogin.
api/queries_issue.go Allow assigneeLogins param and perform post-create login-based assignment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…adataSurvey

Wire up MultiSelectWithSearch for assignees in MetadataSurvey, replacing
the static MultiSelect that required bulk fetching all assignable actors.
This applies to both gh pr create and gh issue create interactive flows
when selecting assignees via the 'Add metadata' prompt.

Changes:
- Add assigneeSearchFunc parameter to MetadataSurvey
- Skip assignee bulk fetch when search func is available
- New SearchRepoAssignableActors API function for repo-level search
  (create flows have no issue/PR node ID yet)
- New RepoAssigneeSearchFunc in shared editable.go
- Refactor actorsToSearchResult helper shared by both search functions

Co-authored-by: Copilot <[email protected]>
@BagToad BagToad force-pushed the fix/pr-create-assignee-metadata-13000 branch from f11b008 to 11f177a Compare March 24, 2026 00:50
@BagToad BagToad requested a review from babakks March 24, 2026 01:09
@BagToad BagToad changed the title WIP: Use login-based assignee mutation on github.com Use login-based assignee mutation on github.com Mar 24, 2026
Comment on lines 186 to 196
useReviewerSearch := state.ActorReviewers && reviewerSearchFunc != nil
useAssigneeSearch := state.ActorAssignees && assigneeSearchFunc != nil
metadataInput := api.RepoMetadataInput{
Reviewers: isChosen("Reviewers") && !useReviewerSearch,
TeamReviewers: isChosen("Reviewers") && !useReviewerSearch,
Assignees: isChosen("Assignees"),
ActorAssignees: isChosen("Assignees") && state.ActorAssignees,
Assignees: isChosen("Assignees") && !useAssigneeSearch,
ActorAssignees: isChosen("Assignees") && !useAssigneeSearch && state.ActorAssignees,
Labels: isChosen("Labels"),
ProjectsV1: isChosen("Projects") && projectsV1Support == gh.ProjectsV1Supported,
ProjectsV2: isChosen("Projects"),
Milestones: isChosen("Milestone"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there no ReviewerActors check? ActorReviewers?

Copy link
Member

@babakks babakks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Reviewed in sync.

…lot expansion

The inline replaceSpecialAssigneeNames closures in AssigneeIds and
AssigneeLogins were duplicated. Extract them into an exported
SpecialAssigneeReplacer type that consolidates MeReplacer and
CopilotReplacer into a single ReplaceSlice call, parameterised by
actorAssignees and copilotUseLogin.

Adopt the new type in the issue create flow as well, replacing the
manual MeReplacer + conditional CopilotReplacer sequence.

Co-authored-by: Copilot <[email protected]>
@BagToad
Copy link
Member Author

BagToad commented Mar 25, 2026

Reviewer/assignee actor-mode asymmetry

Leaving a note here for future context since this came up during review.

When GitHub.com introduced actor-based APIs (the ability to assign Copilot, bots, etc.), we needed to change how the CLI fetches, displays, and mutates assignees and reviewers. The old GHES path resolves everything through node IDs: you fetch all assignable users, present them, and send IDs to the mutation. The new actor path works with logins directly and supports search-based selection.

I wired up the assignee side of this pretty thoroughly, but the reviewer side ended up taking a different shape, and honestly I'm frustrated with the asymmetry. Here's what I mean:

How assignees work:

There are two flows, create (pr create, issue create) and edit (pr edit, issue edit), but the actor-mode signal is threaded through both:

  1. Feature detectionissueFeatures.ActorIsAssignable is checked in both create and edit entry points
  2. Carrying the signal:
    • Create flows → set IssueMetadataState.ActorAssignees on the state struct that gets passed through to the survey and mutation
    • Edit flows → set EditableAssignees.ActorAssignees on the editable struct, which carries the flag through the survey, FetchOptions, and into mutation via AssigneeIds/AssigneeLogins methods
  3. Metadata fetchRepoMetadataInput.ActorAssignees tells the fetch layer to call RepoAssignableActors() instead of RepoAssignableUsers(), getting actors (bots, Copilot, etc.) alongside regular users. This is shared by both flows via MetadataSurveyRepoMetadata.
  4. Survey/UIMetadataSurvey checks ActorAssignees to decide whether to build options from the actor list or the legacy user list, and whether to use search-based selection
  5. Mutationparams.go checks ActorAssignees to send assigneeLogins (login-based) instead of assigneeIds (node-ID-based)

Each layer has an explicit flag that says "we're in actor mode." The flag flows top-to-bottom through both create and edit.

How reviewers work:

✅ = the same as assignees, ❌ = different

  1. Feature detection → Same issueFeatures.ActorIsAssignable
  2. Carrying the signal:
    • Create flows → set IssueMetadataState.ActorReviewers on the state struct ✅
    • Edit flowsEditableReviewers has no ActorReviewers field. There's nothing on the struct to carry this signal. Instead, the edit flow piggybacks on editable.Assignees.ActorAssignees to decide between GraphQL and REST reviewer updates, which is even more confusing since the reviewer actor decision is keyed off an assignee flag. ❌
  3. Metadata fetchRepoMetadataInput has no ActorReviewers field. There's no actor-aware reviewer fetch. When search isn't available, reviewers fall back to AssignableUsers + Teams (the legacy path) regardless of whether we're on github.com or GHES. ❌
  4. Survey/UIMetadataSurvey uses state.ActorReviewers && reviewerSearchFunc != nil for the fetch-skipping decision, so the actor flag is partially involved. But the downstream option-building and selection code only checks reviewerSearchFunc != nil, not the actor flag directly. Compare this to assignees where state.ActorAssignees is checked at every step (fetch input, option building, login extraction). ❌
  5. Mutationparams.go checks IssueMetadataState.ActorReviewers to send logins vs IDs. This part is symmetric. ✅

So the mutation layer and the top-level state are fine, but the middle of the stack (the editable struct, the metadata fetch, and the survey option building) all have the assignee actor path but not the reviewer equivalent. Reviewers work today because search-func-presence happens to be a reliable proxy for "are we in actor mode," but it's a different pattern than what assignees use.

What should we do about it?

I'm not sure the right fix is just adding ActorReviewers to mirror ActorAssignees at every layer. That would make things symmetric but also doubles the surface area. What I'm wondering is whether we should instead have a single capability signal, something like an ActorAPIs or ApiActors flag, that both assignees and reviewers key off of. The feature detection is already shared (ActorIsAssignable gates both), so having one flag that means "this instance supports actor-based APIs" and grouping all the actor-vs-legacy branching under that feels cleaner than per-entity flags threaded through every layer.

Not addressing in this PR since the scope is assignee metadata for create/edit flows. The reviewer paths work correctly. I'll open a follow-up PR to tackle this frustrating asymmetry.

@BagToad BagToad merged commit 1df6f84 into trunk Mar 25, 2026
11 checks passed
@BagToad BagToad deleted the fix/pr-create-assignee-metadata-13000 branch March 25, 2026 02:29
BagToad added a commit that referenced this pull request Mar 25, 2026
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 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding metadata during pr create will cause create to fail if assignee flag present

3 participants