Azure DevOps
How kinhin talks to Azure Pipelines agent pools, and how that compares with Microsoft's current REST API docs.
← Home · Forge reference index · Setup guide
Planned. Azure DevOps support is not part of the beta. This page records the implementation for the work ahead; see the roadmap.
Overview
| Forge id | azuredevops |
| Hosting | Azure DevOps Services (https://dev.azure.com/{org}) or Azure DevOps Server (self-hosted). instance is required and is the organization (or collection) URL. |
| Scope | An agent pool name. |
| Source | Sources/KinhinKit/Forges/AzureDevOps/AzureDevOpsClient.swift, AzureDevOpsForge.swift, AzureDevOpsModels.swift; agent start-up in Sources/KinhinKit/Images/RunnerBootstrap/RunnerBootstrap+AzureDevOps.swift |
| Status | Planned, not supported yet. Implemented and unit-tested; not yet run against the real service. |
Auth
HTTP Basic with an empty user name and the PAT as the password: Authorization: Basic base64(":" + PAT) (AzureDevOpsClient.swift:136). A rejected PAT can come back as a 203, an HTML sign-in page, or a redirect to the sign-in host (which the session refuses to follow, so the 3xx itself arrives); the client treats all three as unauthorized (AzureDevOpsClient.rejectSignInPage). TokenRequirements.azureDevOps lists the permission (Agent Pools read and manage).
Microsoft's OAuth scopes line up: vso.agentpools for reading pools, agents and job state, vso.agentpools_manage for deleting agents.
API version and base URL
| We send | Documented (verified 2026-10-01) | |
|---|---|---|
| Version | api-version=7.0 on every URL (AzureDevOpsClient.swift:15,112) | The version must be sent on every request. |
| Current | n/a | 7.1 is the stable reference version in the docs; 7.2 reference pages also exist. |
Which products accept which version (Microsoft's REST API versioning page):
| Product | Highest REST version |
|---|---|
| Azure DevOps Services | 7.0+ (docs also cover 7.1 and 7.2) |
| Azure DevOps Server 2022 | 7.0 |
| Azure DevOps Server 2020 | 6.0 |
| Azure DevOps Server 2019 | 5.0 |
Per-operation reference pages for Azure DevOps Server exist for 6.0, 7.0 and 7.1 (azure-devops-server-rest-7.1), so a newer Server release than 2022 ships API 7.1. Which Server release that is was not stated on the pages fetched.
Endpoints used
| Method | Path | Purpose | Code |
|---|---|---|---|
| GET | /_apis/distributedtask/pools?poolName=X&actionFilter=manage | Resolve the pool and check manage rights; falls back to no actionFilter to tell "not found" from "cannot manage" | AzureDevOpsClient.swift:33,40 |
| GET | /_apis/distributedtask/pools?actionFilter=manage | Pool picker | AzureDevOpsClient.swift:51 |
| GET | /_apis/distributedtask/pools/{id}/agents?includeAssignedRequest=true | List agents | AzureDevOpsClient.swift:62 |
| DELETE | /_apis/distributedtask/pools/{id}/agents/{aid} | Remove an agent (a 404 is treated as already gone) | AzureDevOpsClient.swift:70 |
| GET | /_apis/distributedtask/pools/{id}/jobrequests?completedRequestCount=0 | Demand and running jobs | AzureDevOpsClient.swift:82 |
Pipeline view
The running job request names its build (owner.id), project (scopeId) and timeline job (jobId). The view reads GET /{project}/_apis/build/builds/{id}/timeline (Job records are the jobs, their Task children the steps) and /builds/{id} for the title, branch and link. This request-to-build link is undocumented (like the job requests endpoint itself); a request without it shows no detail.
Output is GET /{project}/_apis/build/builds/{id}/logs/{logId}, with the log id taken from the timeline record of the task (or job).
Registration flow
- Registration mode
sharedLongLived, exit semanticsderegisters(AzureDevOpsForge.swift:28-29). - The registration credential is the PAT itself:
mintRegistrationTokenreturnscredentials.token. The agent is configured in the foreground withconfig.sh --unattended --url --auth pat --pool --agent, the token passed throughVSTS_AGENT_INPUT_TOKENfor that command only, so a failed registration fails the script with the tail ofagent.log. Registration is sequential, one runner after another. The registered agent then runs in the background (run.sh --oncewhen the instance is ephemeral, plainrun.shwhenvm.ephemeralis false). The agent does not remove itself: an in-guestconfig.sh removewould need the PAT for the whole job, where job code could read it. The script unsets the PAT as soon asconfig.shreturns, and the host deletes the agent's record once it goes offline (below). The PAT still reaches every runner VM for registration, whichTokenRequirementsalready warns about.
Demand and label matching
demands(_:include:)requires a request's demands to include every label. A barenamematches any demand with that name;name=valuematchesname -equals value, case-insensitive.- Labels are exported to the agent as capabilities (
name=trueorname=value). The pool's labels join the account's (the account wins on a name clash), and every name is made a valid environment variable name, sokinhin-dockerbecomeskinhin_docker=true. A job on a docker pool must therefore demand bothkinhinandkinhin_docker. Akinhin_dockerdemand is translated back to thekinhin-dockermarker for routing. - Queued means
assignTime == nilwith no result and no reserved agent: a request already reserved for an agent is not demand for another. A request demandingAgent.OS -equalssomething other than Darwin or Linux (Windows) is not demand either, since no guest could take it. - Running means
assignTime != nilwith no result and a reserved agent. - An agent seen online that then goes offline is reported gone and its record deleted by the host. This is how a finished agent leaves the pool, and the "finished" signal the instance's exit watch waits for. An idle offline agent (its one job done) is reaped after 15 s; one still holding a job (a heartbeat blip) gets 60 s.
Pagination, rate limits, retries
No pagination: every list is a single {value: [...]} read. withTransientRetry on network errors, 429 and 5xx. No rate-limit header handling.
Self-hosted notes and minimum versions
- For Azure DevOps Server,
instanceis the organization or collection URL the user supplies; the exact URL shape for Server was not verified here. api-version=7.0works on Services and Server 2022 only. Server 2020 and earlier reject it.
Version sensitivity
| Item | Detail |
|---|---|
api-version | 7.0 fits Services and Server 2022. Server 2020 tops out at 6.0; Server 2019 at 5.0. Newer Server releases offer 7.1. |
| Endpoints used | The pool and agent operations exist in the 5.1, 6.0, 6.1, 7.0 and 7.1 references, so lowering api-version for older Server is plausible. Not tested. |
| Preview versions | After a release, its -preview form is deprecated and can be removed after 12 weeks. kinhin uses only stable versions. |
Before support: to verify against a live service
- Highest-impact risk: an empty pool plus a
demands:job may fail fast rather than queue. Azure may reject a request whose demands no agent in the pool satisfies instead of leaving it waiting, so kinhin would never see demand to scale up from. If so, the pool needs a placeholder offline agent that advertises the capabilities, ormin_runners >= 1. Not verified. - That an exported
kinhin_docker=trueenvironment variable is picked up as a capability by the agent, and that the demand arrives askinhin_docker. - That
installdependencies.shundersudosucceeds on the Tart Linux image and thatAgent.Listener --versionruns there. - That the 60 s busy grace never reaps an agent mid-job, and how often an idle agent blips offline before its first job (each such blip costs a respawn under the 15 s idle grace).
- That a request carries
reservedAgentbeforeassignTime, as the queued rule assumes. - That a redirect really is how an expired PAT presents on Azure DevOps Services and Server.
- That sequential registration of several runners in one VM is fast enough.
Verified
2026-10-01, against:
- https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/rest-api-versioning
- https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/pools/get-agent-pools?view=azure-devops-rest-7.1
- https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/agents?view=azure-devops-rest-7.1
- https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/agents/delete?view=azure-devops-rest-7.1
- https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/requests/list?view=azure-devops-rest-7.1
