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
List classic build records in a project with filter and pagination support. This is the Build-v1 surface (XAML and early YAML); for the modern Pipelines "runs" surface, see azdo pipelines runs list (#214). The Azure CLI command fetches a []Build from the Build REST API and the azdo command mirrors that contract while exposing JSON output and a deterministic table layout.
GET {organization}/{project}/_apis/build/builds?api-version=7.1
Locked Decisions (do not re-derive)
#
Decision
Rationale
1
Use the vendored build.Client.GetBuilds SDK method (vendor/.../build/client.go:1659); do not hand-roll HTTP.
SDK has every filter we need (Definitions, BranchName, BuildNumber, Top, ContinuationToken, ResultFilter, StatusFilter, ReasonFilter, TagFilters, RequestedFor, QueryOrder, MinTime/MaxTime, RepositoryId/RepositoryType, BuildIds, MaxBuildsPerDefinition, DeletedFilter).
2
Mock for GetBuilds already exists at internal/mocks/build_client_mock.go:701-714; no new mock generation required.
Confirmed via grep; satisfies the "reuse-first" rule.
3
Resolve client via ctx.ClientFactory().Build(ctx.Context(), scope.Organization).
Matches the factory pattern used by internal/cmd/pipelines/variablegroup/list/list.go and internal/cmd/boards/workitem/list/list.go.
4
Use the [ORGANIZATION/]PROJECT positional pattern via util.ParseProjectScope (defined in internal/cmd/util/scope.go:78-108); fall back to the configured default organization when the segment is omitted.
Project-scoped commands must mirror the convention from AGENTS.md.
5
Loop on ContinuationToken until empty (default behavior).
SDK returns a value-type ContinuationToken string; the loop is bounded by an empty string.
6
--top is not an alias for --limit — pass it through to args.Top directly so server-side paging semantics match Azure CLI.
az pipelines build list --top 50 returns the most recent 50 builds; the server applies ordering, so a Top larger than the available set is fine.
7
--max-items is a client-side cap applied after the SDK loop terminates (default 0 = unlimited).
Mirrors azdo's convention from internal/cmd/pipelines/variablegroup/list/list.go; respects AGENTS.md "Pagination for List Operations".
8
--definition-id, --branch, --tag, --status, --result, --reason are repeatable / comma-aware but pass through to the SDK as scalars / slices.
SDK accepts slices for some and a single value for others; we keep the CLI surface simple by collapsing repeated invocations into a slice (or a single value for status/result/reason per call).
9
Default sort: server-determined (no QueryOrder set).
Matches az pipelines build list default in the Python extension.
10
When opts.exporter != nil, emit the raw []build.Build slice to JSON; do not invent an intermediate view struct unless filtering/redaction is required.
Build model fields are already JSON-tagged and stable; no redaction needed for a list view.
azdo pipelines build is a new Cobra subgroup under internal/cmd/pipelines; tracked in #211.
The existing internal/cmd/pipelines/pipelines.go only registers variablegroup.
Command Signature
azdo pipelines build list [ORGANIZATION/]PROJECT [flags]
Aliases:
ls, l
Flags (mapped to SDK/REST)
Flag
Maps to
Notes
--definition-id intSlice
GetBuildsArgs.Definitions
The Python extension uses --definition-ids (plural); we expose the singular per azdo convention and accept it repeated.
--branch stringSlice
GetBuildsArgs.BranchName
The SDK accepts a single value per call so only the first is honored (the Python extension has the same limitation). Bare branch names get refs/heads/ prefix.
--build-number string
GetBuildsArgs.BuildNumber
Append * for prefix search (server-side).
--status stringSlice
GetBuildsArgs.StatusFilter
The SDK accepts a single value so only the first is honored. Valid values: none, inProgress, completed, cancelling, postponed, notStarted, all.
--result stringSlice
GetBuildsArgs.ResultFilter
The SDK accepts a single value so only the first is honored. Valid values: none, succeeded, partiallySucceeded, failed, canceled.
--reason stringSlice
GetBuildsArgs.ReasonFilter
The SDK accepts a single value so only the first is honored. Valid values: manual, individualCI, batchedCI, schedule, scheduleForced, userCreated, validateShelveset, checkInShelveset, pullRequest, buildCompletion, resourceTrigger, triggered, all.
--tag stringSlice
GetBuildsArgs.TagFilters
Builds must have all specified tags (AND semantics).
--requested-for string
GetBuildsArgs.RequestedFor
Resolves @me via the Extensions client. Same pattern as internal/cmd/boards/workitem/list/list.go:506-520.
--top int
GetBuildsArgs.Top
Pass-through to server. Defaults to server's page size if unset.
--max-items int
Client-side cap. Applied after the SDK loop.
--max-items 0 means unlimited (default).
--json / --jq / --template
util.AddJSONFlags registration
JSON fields list matches the Build model struct tags.
When opts.exporter != nil, emit the raw []build.Build returned by GetBuilds after the ContinuationToken loop. Do not introduce a view struct; the SDK's Build already carries JSON tags and no field redaction is required for a list view.
§4. Test fixture (copy from internal/cmd/boards/workitem/list/list_test.go:765-844 + a Build-client stub)
The hermetic setupFakeDeps fixture used by the boards work-item list test handles a fake ConnectionFactory, a fake ClientFactory, and a fake IOStreams. Add a setupBuildFakeDeps(t, organization) variant that stubs MockBuildClient.GetBuilds with a GetBuildsResponseValue containing a []Build payload. Reuse the gomock patterns from internal/cmd/boards/workitem/list/list_test.go:765-844. The Build mock is internal/mocks/build_client_mock.go (see MockBuildClient).
API Surface
build.Client.GetBuilds(ctx, GetBuildsArgs) (*GetBuildsResponseValue, error) — see vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1659.
GetBuildsResponseValue — see vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1803-1806. Fields: Value []Build, ContinuationToken string (value type, not pointer).
TestList_EmptyResult — stub GetBuilds to return {Value: nil, ContinuationToken: ""} and assert the table renderer prints headers only, no error.
TestList_NoFilters — stub a []Build of 3 items; assert all 3 are rendered in order.
TestList_ContinuationToken_Loops — stub page 1 returning ContinuationToken: "tok-1" with 2 items, then page 2 returning ContinuationToken: "" with 1 item; assert all 3 items collected and GetBuilds called twice with the same args (token reset across calls).
TestList_FiltersPassedToSDK — set --definition-id 1 --definition-id 2 --branch main --status completed --result succeeded --reason manual --tag release --requested-for @me --top 50; assert the recorded GetBuildsArgs contains Definitions=[1,2], BranchName=refs/heads/main, StatusFilter=completed, ResultFilter=succeeded, ReasonFilter=manual, TagFilters=[release], RequestedFor=, Top=50.
TestList_MaxItemsCap — stub 5 items; run with --max-items 3; assert 3 items rendered and only one GetBuilds call recorded.
TestList_JSONOutput — set --json id,buildNumber,status; assert the JSON output contains only those keys.
TestList_PaginationAcrossPages_CappedByMaxItems — stub 2 pages of 5 items each; --max-items 6; assert only 6 items rendered.
TestList_RequestedFor_ResolvesAtMe — stub the Extensions client; assert RequestedFor equals the resolved ID.
TestList_InvalidStatus — --status nonsense; expect util.FlagErrorf with a message listing the valid values.
TestList_ScopeArg_ParsesOrgSlashProject — "myOrg/myProject"; assert args.Project is "myProject" and the org used in ClientFactory.Build(...) is "myOrg".
Use setupFakeDeps pattern from internal/cmd/boards/workitem/list/list_test.go:765-844 plus a MockBuildClient wired via the MockClientFactory (same wiring style as work-item list test).
Phase 2 — GREEN (minimal implementation).
Implement list.go per the §3 skeleton; no new helpers outside what is sketched.
Reuse util.ParseProjectScope, util.FlagErrorWrap, util.AddJSONFlags, types.GetValue, types.Unique. No new helpers unless mandated.
If @me resolution is needed, reuse the ExtensionsClient.GetSelfID + IdentityClient.ReadIdentities chain from internal/cmd/boards/workitem/list/list.go:506-520.
For branch prefixing, inline a 3-line resolveGitBranchRef helper in list.go (do not promote to a shared package).
Keep the file under ~250 LOC.
Tooling and Verification Checklist
gofmt -w and goimports -w on the new files.
gofumpt -w after the implementation lands (acceptable to lag during drafts).
go build ./... — must succeed.
go test ./... — must pass.
make lint — must pass.
make docs — must regenerate without warnings; new flags appear in docs/pipelines_build_list.md.
Command Description
List classic build records in a project with filter and pagination support. This is the Build-v1 surface (XAML and early YAML); for the modern Pipelines "runs" surface, see
azdo pipelines runs list(#214). The Azure CLI command fetches a[]Buildfrom the Build REST API and theazdocommand mirrors that contract while exposing JSON output and a deterministic table layout.Locked Decisions (do not re-derive)
build.Client.GetBuildsSDK method (vendor/.../build/client.go:1659); do not hand-roll HTTP.Definitions,BranchName,BuildNumber,Top,ContinuationToken,ResultFilter,StatusFilter,ReasonFilter,TagFilters,RequestedFor,QueryOrder,MinTime/MaxTime,RepositoryId/RepositoryType,BuildIds,MaxBuildsPerDefinition,DeletedFilter).GetBuildsalready exists atinternal/mocks/build_client_mock.go:701-714; no new mock generation required.grep; satisfies the "reuse-first" rule.ctx.ClientFactory().Build(ctx.Context(), scope.Organization).internal/cmd/pipelines/variablegroup/list/list.goandinternal/cmd/boards/workitem/list/list.go.[ORGANIZATION/]PROJECTpositional pattern viautil.ParseProjectScope(defined ininternal/cmd/util/scope.go:78-108); fall back to the configured default organization when the segment is omitted.AGENTS.md.ContinuationTokenuntil empty (default behavior).ContinuationToken string; the loop is bounded by an empty string.--topis not an alias for--limit— pass it through toargs.Topdirectly so server-side paging semantics match Azure CLI.az pipelines build list --top 50returns the most recent 50 builds; the server applies ordering, so aToplarger than the available set is fine.--max-itemsis a client-side cap applied after the SDK loop terminates (default0= unlimited).azdo's convention frominternal/cmd/pipelines/variablegroup/list/list.go; respects AGENTS.md "Pagination for List Operations".--definition-id,--branch,--tag,--status,--result,--reasonare repeatable / comma-aware but pass through to the SDK as scalars / slices.QueryOrderset).az pipelines build listdefault in the Python extension.opts.exporter != nil, emit the raw[]build.Buildslice to JSON; do not invent an intermediate view struct unless filtering/redaction is required.ID,NUMBER,STATUS,RESULT,REASON,DEFINITION,BRANCH,REQUESTED FOR,STARTED,FINISHED.az pipelines build listdefault; uses*Buildfields:Id,BuildNumber,Status,Result,Reason,Definition.Name,SourceBranch,RequestedFor.DisplayName,StartTime,FinishTime.RequestedBy,RequestedFor) displayIdentityRef.DisplayName(fallback toUniqueName); usetypes.GetValuefor nil-safety.internal/cmd/boards/workitem/list/list.go:1074-1080.2006-01-02 15:04:05 MSTlocal time; UTC for empty values.internal/cmd/boards/workitem/list/list.go:212-214.azdo pipelines buildis a new Cobra subgroup underinternal/cmd/pipelines; tracked in #211.internal/cmd/pipelines/pipelines.goonly registersvariablegroup.Command Signature
Flags (mapped to SDK/REST)
--definition-id intSliceGetBuildsArgs.Definitions--definition-ids(plural); we expose the singular perazdoconvention and accept it repeated.--branch stringSliceGetBuildsArgs.BranchNamerefs/heads/prefix.--build-number stringGetBuildsArgs.BuildNumber*for prefix search (server-side).--status stringSliceGetBuildsArgs.StatusFilternone,inProgress,completed,cancelling,postponed,notStarted,all.--result stringSliceGetBuildsArgs.ResultFilternone,succeeded,partiallySucceeded,failed,canceled.--reason stringSliceGetBuildsArgs.ReasonFiltermanual,individualCI,batchedCI,schedule,scheduleForced,userCreated,validateShelveset,checkInShelveset,pullRequest,buildCompletion,resourceTrigger,triggered,all.--tag stringSliceGetBuildsArgs.TagFilters--requested-for stringGetBuildsArgs.RequestedFor@mevia the Extensions client. Same pattern asinternal/cmd/boards/workitem/list/list.go:506-520.--top intGetBuildsArgs.Top--max-items int--max-items 0means unlimited (default).--json/--jq/--templateutil.AddJSONFlagsregistrationBuildmodel struct tags.JSON Output Contract
When
opts.exporter != nil, emit the raw[]build.Buildreturned byGetBuildsafter theContinuationTokenloop. Do not introduce a view struct; the SDK'sBuildalready carries JSON tags and no field redaction is required for a list view.Command Wiring
internal/cmd/pipelines/build/list/list.go,list_test.gointernal/cmd/pipelines/build/build.go(filed in feat: Implementazdo pipelines build listcommand #211) mustAddCommand(list.NewCmd(ctx)).internal/cmd/pipelines/pipelines.gomust register thebuildsubgroup (filed in feat: Implementazdo pipelines build listcommand #211) before this leaf is reachable.make docs.Code Skeleton (canonical, copy verbatim)
§1.
listOptionsstruct§2.
NewCmdshape (no surprises)§3.
runListskeleton§4. Test fixture (copy from
internal/cmd/boards/workitem/list/list_test.go:765-844+ a Build-client stub)The hermetic
setupFakeDepsfixture used by the boardswork-item listtest handles a fakeConnectionFactory, a fakeClientFactory, and a fakeIOStreams. Add asetupBuildFakeDeps(t, organization)variant that stubsMockBuildClient.GetBuildswith aGetBuildsResponseValuecontaining a[]Buildpayload. Reuse thegomockpatterns frominternal/cmd/boards/workitem/list/list_test.go:765-844. The Build mock isinternal/mocks/build_client_mock.go(seeMockBuildClient).API Surface
build.Client.GetBuilds(ctx, GetBuildsArgs) (*GetBuildsResponseValue, error)— seevendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1659.GetBuildsArgs— seevendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1757-1802. Fields used:Project,Definitions,BranchName,BuildNumber,StatusFilter,ResultFilter,ReasonFilter,TagFilters,RequestedFor,Top,ContinuationToken.GetBuildsResponseValue— seevendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1803-1806. Fields:Value []Build,ContinuationToken string(value type, not pointer).Buildmodel — seevendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/models.go:122-217. Fields used:Id,BuildNumber,Status,Result,Reason,Definition,Project,SourceBranch,SourceVersion,StartTime,FinishTime,QueueTime,RequestedBy,RequestedFor,Tags,Uri,URL.BuildStatusValues—vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/models.go:1062-1080.BuildResultValues—vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/models.go:978-985.BuildReasonValues—vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/models.go:862-887.internal/mocks/build_client_mock.go:701-714(MockBuildClient.GetBuilds).internal/azdo/connection.go:50-51(ClientFactory.Build);internal/azdo/factory.go:61(impl).Implementation Approach (TDD, reuse-first, minimal)
Phase 1 — RED (tests first).
internal/cmd/pipelines/build/list/list_test.go:TestList_EmptyResult— stubGetBuildsto return{Value: nil, ContinuationToken: ""}and assert the table renderer prints headers only, no error.TestList_NoFilters— stub a[]Buildof 3 items; assert all 3 are rendered in order.TestList_ContinuationToken_Loops— stub page 1 returningContinuationToken: "tok-1"with 2 items, then page 2 returningContinuationToken: ""with 1 item; assert all 3 items collected andGetBuildscalled twice with the same args (token reset across calls).TestList_FiltersPassedToSDK— set--definition-id 1 --definition-id 2 --branch main --status completed --result succeeded --reason manual --tag release --requested-for @me --top 50; assert the recordedGetBuildsArgscontainsDefinitions=[1,2],BranchName=refs/heads/main,StatusFilter=completed,ResultFilter=succeeded,ReasonFilter=manual,TagFilters=[release],RequestedFor=,Top=50.TestList_MaxItemsCap— stub 5 items; run with--max-items 3; assert 3 items rendered and only oneGetBuildscall recorded.TestList_JSONOutput— set--json id,buildNumber,status; assert the JSON output contains only those keys.TestList_PaginationAcrossPages_CappedByMaxItems— stub 2 pages of 5 items each;--max-items 6; assert only 6 items rendered.TestList_RequestedFor_ResolvesAtMe— stub the Extensions client; assertRequestedForequals the resolved ID.TestList_InvalidStatus—--status nonsense; expectutil.FlagErrorfwith a message listing the valid values.TestList_ScopeArg_ParsesOrgSlashProject—"myOrg/myProject"; assertargs.Projectis"myProject"and the org used inClientFactory.Build(...)is"myOrg".setupFakeDepspattern frominternal/cmd/boards/workitem/list/list_test.go:765-844plus aMockBuildClientwired via theMockClientFactory(same wiring style aswork-item listtest).Phase 2 — GREEN (minimal implementation).
list.goper the §3 skeleton; no new helpers outside what is sketched.util.ParseProjectScope,util.FlagErrorWrap,util.AddJSONFlags,types.GetValue,types.Unique. No new helpers unless mandated.@meresolution is needed, reuse theExtensionsClient.GetSelfID+IdentityClient.ReadIdentitieschain frominternal/cmd/boards/workitem/list/list.go:506-520.resolveGitBranchRefhelper inlist.go(do not promote to a shared package).Tooling and Verification Checklist
gofmt -wandgoimports -won the new files.gofumpt -wafter the implementation lands (acceptable to lag during drafts).go build ./...— must succeed.go test ./...— must pass.make lint— must pass.make docs— must regenerate without warnings; new flags appear indocs/pipelines_build_list.md.Reference Existing Patterns
internal/cmd/boards/workitem/list/list.go—NewCmdshape,util.ParseProjectScopeusage, identity resolution, table rendering, JSON output.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDepstest fixture (canonical hermetic pattern).internal/cmd/pipelines/variablegroup/list/list.go— pipelines-scoped list command; same Cobra wiring.internal/mocks/build_client_mock.go:701-714— pre-existingGetBuildsmock.internal/azdo/connection.go:50-51—ClientFactory.Buildaccessor.internal/cmd/boards/workitem/list/list.go:1062-1080—fieldIdentityDisplayforIdentityRefrendering.References
build_list.commands.py#L84-L92.vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/client.go:1659(GetBuilds).vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/build/models.go:122-217(Build),:1062-1080(BuildStatusValues),:978-985(BuildResultValues),:862-887(BuildReasonValues).internal/mocks/build_client_mock.go:701-714(MockBuildClient.GetBuilds).azdo pipelines build listcommand #211 (build subgroup).