You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Azure DevOps teams are backed by security groups, but the Azure DevOps REST API does not expose direct "add/remove member" endpoints for a team. az do pipelines and az devops work around this by either wrapping the Graph API on the team's underlying security group, or omitting the commands entirely. azdo should fill this gap by introducing an opinionated team member subgroup that wraps graph.Client.AddMembership / RemoveMembership against the team's Identity.SubjectDescriptor, and moves the existing team list-member command into the new subgroup as team member list for consistent subgroup ergonomics.
This umbrella establishes the new azdo team member subgroup beneath the existing azdo team group (#221). It does NOT add a team-member admin subgroup (out of scope; team administrators are managed via a separate security group inside the team — see "Out of scope" below).
Subgroup location: internal/cmd/team/member/member.go defines NewCmd(ctx util.CmdContext) *cobra.Command with Use: "member <command>", Short: "Manage members of a team.", and wires add.NewCmd(ctx), remove.NewCmd(ctx), list.NewCmd(ctx).
Wired from: internal/cmd/team/team.go — replace cmd.AddCommand(listmember.NewCmd(ctx)) with cmd.AddCommand(member.NewCmd(ctx)); remove the listmember import.
Aliases on the subgroup itself: none (mirrors the security group membership subgroup convention).
No backward-compat alias for the old azdo team list-member form (clean move per user direction 2026-06-06).
Member-input acceptance: all 3 leaves accept user/group identifiers through the existing extensions.Client.ResolveSubject helper (internal/azdo/extensions/member_lookup.go:19), which already handles subject descriptors, email addresses, principal names, SIDs, and identity IDs.
Team target parsing: [ORGANIZATION/]PROJECT/TEAM (project-scoped; same parser as the existing team list-member and team show commands).
All 3 leaves are project-scoped (teams are project-scoped in AzDO).
No new SDK client, no new mocks: every primitive is already vendored and mocked.
Command Signature
Command
Use
Aliases
Notes
add
add [ORGANIZATION/]PROJECT/TEAM
["a"]
Requires --user; idempotent (no error if already a member).
remove
remove [ORGANIZATION/]PROJECT/TEAM
["r", "rm", "del", "d"]
Requires --user; destructive with confirmation prompt unless --yes.
list
list [ORGANIZATION/]PROJECT/TEAM
["members", "ls", "l"]
MOVE of the existing team list-member (#220). Pure refactor, no behavior change.
Subgroup Wiring
internal/cmd/team/member/member.go registers all 3 leaves.
internal/cmd/team/team.go is updated to import the new member package and remove the listmember import.
After all three leaves are merged, the umbrella implementation step is simply: cmd.AddCommand(member.NewCmd(ctx)) in team.go (replacing cmd.AddCommand(listmember.NewCmd(ctx))).
Run go build ./..., make lint, go test ./..., and make docs.
Out of Scope (explicit non-goals)
Team administrator management (e.g. azdo team admin add/remove). Team administrators are managed via a separate security group inside the team; the API requires a different call path (mutating a separate "Administrators" group, not the team's primary group). If needed, file a follow-up issue titled Introduce azdo team admin command group that follows the same pattern. This is intentionally NOT bundled here to keep scope tight.
Group-as-member (adding an AzDO security group as a team member). The graph API supports it (subject kind = "Group"), but the existing extensionsClient.ResolveSubject already handles group descriptors transparently, so this works without a new code path. Documentation-only concern.
Bulk add/remove (e.g. --user user1,user2,user3). The single-user-per-call design matches the existing security group membership add precedent in the codebase; bulk can be added later as a non-breaking enhancement.
Project-level membership management (e.g. azdo project member add). Out of scope; the user explicitly asked about team members.
Why this is a new azdo capability (not in reference CLIs)
MS az devops team: only has create / delete / list / list-member / show / update. The official Microsoft Learn docs say: "There's no comparable command for adding users to a team or project."
Python AzDO Extension (Azure/azure-devops-cli-extension): 0 occurrences of add_team_member / remove_team_member.
AzDO MCP Server (microsoft/azure-devops-mcp): 0 dedicated team-member management tools.
Vendored Go SDK core client: has GetTeamMembersWithExtendedProperties but no add/remove methods. The actual mechanism is graph.Client.AddMembership / RemoveMembership on the team's underlying security group.
So this issue introduces a newazdo capability that is technically possible (via the Graph API) but not implemented in any of the three reference CLIs. The implementation is straightforward because the project already has extensionsClient.ResolveSubject for user resolution and core.Client.GetTeam to surface the team's group descriptor.
Command Group Description
Azure DevOps teams are backed by security groups, but the Azure DevOps REST API does not expose direct "add/remove member" endpoints for a team.
az do pipelinesandaz devopswork around this by either wrapping the Graph API on the team's underlying security group, or omitting the commands entirely.azdoshould fill this gap by introducing an opinionatedteam membersubgroup that wrapsgraph.Client.AddMembership/RemoveMembershipagainst the team'sIdentity.SubjectDescriptor, and moves the existingteam list-membercommand into the new subgroup asteam member listfor consistent subgroup ergonomics.This umbrella establishes the new
azdo team membersubgroup beneath the existingazdo teamgroup (#221). It does NOT add a team-member admin subgroup (out of scope; team administrators are managed via a separate security group inside the team — see "Out of scope" below).Locked Decisions
azdo team(existing feat: Implementazdo teamcommand group #221) →azdo team member(new) → 3 leaves (add,remove,list).internal/cmd/team/member/member.godefinesNewCmd(ctx util.CmdContext) *cobra.CommandwithUse: "member <command>",Short: "Manage members of a team.", and wiresadd.NewCmd(ctx),remove.NewCmd(ctx),list.NewCmd(ctx).internal/cmd/team/team.go— replacecmd.AddCommand(listmember.NewCmd(ctx))withcmd.AddCommand(member.NewCmd(ctx)); remove thelistmemberimport.security group membershipsubgroup convention).azdo team list-memberform (clean move per user direction 2026-06-06).extensions.Client.ResolveSubjecthelper (internal/azdo/extensions/member_lookup.go:19), which already handles subject descriptors, email addresses, principal names, SIDs, and identity IDs.[ORGANIZATION/]PROJECT/TEAM(project-scoped; same parser as the existingteam list-memberandteam showcommands).Command Signature
addadd [ORGANIZATION/]PROJECT/TEAM["a"]--user; idempotent (no error if already a member).removeremove [ORGANIZATION/]PROJECT/TEAM["r", "rm", "del", "d"]--user; destructive with confirmation prompt unless--yes.listlist [ORGANIZATION/]PROJECT/TEAM["members", "ls", "l"]team list-member(#220). Pure refactor, no behavior change.Subgroup Wiring
internal/cmd/team/member/member.goregisters all 3 leaves.internal/cmd/team/team.gois updated to import the newmemberpackage and remove thelistmemberimport.internal/cmd/team/listmember/directory is deleted after the move (refactor: Moveazdo team list-membertoazdo team member list#290) is verified (build + tests + lint pass).Sub-Issues
azdo team member addcommand #288 —feat: Implement azdo team member add command(filed 2026-06-06)azdo team member removecommand #289 —feat: Implement azdo team member remove command(filed 2026-06-06)azdo team list-membertoazdo team member list#290 —refactor: Move azdo team list-member to azdo team member list(filed 2026-06-06)Implementation Outline
azdo team member addcommand #288 (add) and feat: Implementazdo team member removecommand #289 (remove) first, then refactor: Moveazdo team list-membertoazdo team member list#290 (list move) last (because refactor: Moveazdo team list-membertoazdo team member list#290'smember.gofile won't compile until feat: Implementazdo team member addcommand #288 and feat: Implementazdo team member removecommand #289 exist).cmd.AddCommand(member.NewCmd(ctx))inteam.go(replacingcmd.AddCommand(listmember.NewCmd(ctx))).go build ./...,make lint,go test ./..., andmake docs.Out of Scope (explicit non-goals)
azdo team admin add/remove). Team administrators are managed via a separate security group inside the team; the API requires a different call path (mutating a separate "Administrators" group, not the team's primary group). If needed, file a follow-up issue titledIntroduce azdo team admin command groupthat follows the same pattern. This is intentionally NOT bundled here to keep scope tight.extensionsClient.ResolveSubjectalready handles group descriptors transparently, so this works without a new code path. Documentation-only concern.--user user1,user2,user3). The single-user-per-call design matches the existingsecurity group membership addprecedent in the codebase; bulk can be added later as a non-breaking enhancement.azdo project member add). Out of scope; the user explicitly asked about team members.Why this is a new azdo capability (not in reference CLIs)
az devops team: only hascreate / delete / list / list-member / show / update. The official Microsoft Learn docs say: "There's no comparable command for adding users to a team or project."Azure/azure-devops-cli-extension): 0 occurrences ofadd_team_member/remove_team_member.microsoft/azure-devops-mcp): 0 dedicated team-member management tools.coreclient: hasGetTeamMembersWithExtendedPropertiesbut no add/remove methods. The actual mechanism isgraph.Client.AddMembership/RemoveMembershipon the team's underlying security group.So this issue introduces a new
azdocapability that is technically possible (via the Graph API) but not implemented in any of the three reference CLIs. The implementation is straightforward because the project already hasextensionsClient.ResolveSubjectfor user resolution andcore.Client.GetTeamto surface the team's group descriptor.References
azdo teamcommand group #221 (azdo teamcommand group)azdo team list-membercommand #220 (will be referenced by refactor: Moveazdo team list-membertoazdo team member list#290 move issue, not modified)azdo team member addcommand #288 (add), feat: Implementazdo team member removecommand #289 (remove), refactor: Moveazdo team list-membertoazdo team member list#290 (list move)internal/azdo/extensions/member_lookup.go:19(ResolveSubject)security group membershipadd:internal/cmd/security/group/membership/add/add.go(primary implementation template for feat: Implementazdo team member addcommand #288)internal/cmd/team/listmember/listmember.gocore/client.go:451(GetTeam),core/client.go:620(GetTeamMembersWithExtendedProperties),graph/client.go:111(AddMembership),graph/client.go:964(RemoveMembership),graph/client.go:724(CheckMembershipExistence)core/models.go:466(WebApiTeam),graph/models.go:157(GraphMembership),webapi/models.go:248(TeamMember)Add a memberto a security group (the underlying mechanism teams use)