You are reading the docs for v0.0.1-beta-2. View the latest docs.
Skip to content

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 idazuredevops
HostingAzure DevOps Services (https://dev.azure.com/{org}) or Azure DevOps Server (self-hosted). instance is required and is the organization (or collection) URL.
ScopeAn agent pool name.
SourceSources/KinhinKit/Forges/AzureDevOps/AzureDevOpsClient.swift, AzureDevOpsForge.swift, AzureDevOpsModels.swift; agent start-up in Sources/KinhinKit/Images/RunnerBootstrap/RunnerBootstrap+AzureDevOps.swift
StatusPlanned, 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 sendDocumented (verified 2026-10-01)
Versionapi-version=7.0 on every URL (AzureDevOpsClient.swift:15,112)The version must be sent on every request.
Currentn/a7.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):

ProductHighest REST version
Azure DevOps Services7.0+ (docs also cover 7.1 and 7.2)
Azure DevOps Server 20227.0
Azure DevOps Server 20206.0
Azure DevOps Server 20195.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 ​

MethodPathPurposeCode
GET/_apis/distributedtask/pools?poolName=X&actionFilter=manageResolve 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=managePool pickerAzureDevOpsClient.swift:51
GET/_apis/distributedtask/pools/{id}/agents?includeAssignedRequest=trueList agentsAzureDevOpsClient.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=0Demand and running jobsAzureDevOpsClient.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 semantics deregisters (AzureDevOpsForge.swift:28-29).
  • The registration credential is the PAT itself: mintRegistrationToken returns credentials.token. The agent is configured in the foreground with config.sh --unattended --url --auth pat --pool --agent, the token passed through VSTS_AGENT_INPUT_TOKEN for that command only, so a failed registration fails the script with the tail of agent.log. Registration is sequential, one runner after another. The registered agent then runs in the background (run.sh --once when the instance is ephemeral, plain run.sh when vm.ephemeral is false). The agent does not remove itself: an in-guest config.sh remove would need the PAT for the whole job, where job code could read it. The script unsets the PAT as soon as config.sh returns, and the host deletes the agent's record once it goes offline (below). The PAT still reaches every runner VM for registration, which TokenRequirements already warns about.

Demand and label matching ​

  • demands(_:include:) requires a request's demands to include every label. A bare name matches any demand with that name; name=value matches name -equals value, case-insensitive.
  • Labels are exported to the agent as capabilities (name=true or name=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, so kinhin-docker becomes kinhin_docker=true. A job on a docker pool must therefore demand both kinhin and kinhin_docker. A kinhin_docker demand is translated back to the kinhin-docker marker for routing.
  • Queued means assignTime == nil with no result and no reserved agent: a request already reserved for an agent is not demand for another. A request demanding Agent.OS -equals something other than Darwin or Linux (Windows) is not demand either, since no guest could take it.
  • Running means assignTime != nil with 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, instance is the organization or collection URL the user supplies; the exact URL shape for Server was not verified here.
  • api-version=7.0 works on Services and Server 2022 only. Server 2020 and earlier reject it.

Version sensitivity ​

ItemDetail
api-version7.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 usedThe 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 versionsAfter 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, or min_runners >= 1. Not verified.
  • That an exported kinhin_docker=true environment variable is picked up as a capability by the agent, and that the demand arrives as kinhin_docker.
  • That installdependencies.sh under sudo succeeds on the Tart Linux image and that Agent.Listener --version runs 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 reservedAgent before assignTime, 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: