NixOS channels and builds
Where nixpkgs stands, as a set of small questions with separate answers: which channels exist, whether a CVE's fix reached them, whether a pull request was backported, when a commit landed in a stable channel and whether Hydra built a package. Each answers from one upstream and says so when that upstream could not be read.
GET /api/v1/nix/channels
GET /api/v1/nix/channels/history?channel=
GET /api/v1/cve/:id/nix/channels
GET /api/v1/nix/pr/:number/backports
GET /api/v1/nix/stable/:series/containment/:sha
GET /api/v1/nix/hydra/:packageChannel overview
GET /api/v1/nix/channelsWhere each channel stands, as NixOS's own monitoring reports it. The channel_revision metric on prometheus.nixos.org carries one row per channel with the revision it points at, its status and its variant; this service reads it every half hour, keeps the latest reading and answers from that. The unstable channel, the current stable release and the previous one are named at the top, every channel the monitoring reports sits under allChannels, and basis: "observed" says the answer is that reading. There is no data envelope: the channels sit at the top level.
{
"success": true,
"basis": "observed",
"source": "prometheus.nixos.org",
"sourceUrl": "https://prometheus.nixos.org/api/v1/query?query=channel_revision",
"observedAt": "2026-09-28T02:30:04.118Z",
"evaluatedAt": "2026-09-28T02:30:03.902Z",
"unchangedSince": "2026-09-27T22:00:03.551Z",
"snapshot": "4f9c1e7a3b2d8e6f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071",
"timestamp": "2026-09-28T02:41:12.006Z",
"channels": {
"unstable": { "name": "nixos-unstable", "version": "unstable", "branch": "nixos-unstable", "revision": "e158d9ed9b51c98974c5e66e1ba1c9e0255fecaa", "latestCommit": "e158d9e", "status": "rolling", "variant": "primary" },
"currentStable": { "name": "nixos-26.05", "version": "26.05", "branch": "release-26.05", "revision": "5e2305d577ca00acbba631b05cb1094d172b29f3", "latestCommit": "5e2305d", "status": "stable", "variant": "primary" },
"previousStable": { "name": "nixos-25.11", "version": "25.11", "branch": "release-25.11", "revision": "b6018f87da91d19d0ab4cf979885689b469cdd41", "latestCommit": "b6018f8", "status": "unmaintained", "variant": "primary" }
},
"allChannels": [
{ "name": "nixos-25.11", "version": "25.11", "branch": "release-25.11", "revision": "b6018f87da91d19d0ab4cf979885689b469cdd41", "latestCommit": "b6018f8", "status": "unmaintained", "variant": "primary" },
{ "name": "nixos-25.11-small", "version": "25.11", "branch": "release-25.11", "revision": "c9bfd86ed684d27e63b0ff9ebb18699f84f27a3b", "latestCommit": "c9bfd86", "status": "unmaintained", "variant": "small" },
{ "name": "nixpkgs-unstable", "version": "unstable", "branch": "nixpkgs-unstable", "revision": "3181085bfd08663b6b9e60bc7a8395c2aaa741bd", "latestCommit": "3181085", "status": "rolling", "variant": null }
],
"history": "/api/v1/nix/channels/history?channel={name}",
"nextRelease": { "version": "26.11", "expectedDate": "2026-11-30", "daysUntil": 63 }
}| Field | Meaning |
|---|---|
observedAt | When this service last read the channel set. |
evaluatedAt | The sample time Prometheus reported for that reading. |
unchangedSince | When this exact channel set was first seen. A set that has stood for days and one read a moment ago both carry a fresh observedAt; this tells them apart. |
snapshot | The SHA-256 digest of the channel rows, which names the archived snapshot. It proves the rows are what this service normalised from the response it received, not that prometheus.nixos.org sent them: the endpoint signs nothing. |
status | As the monitoring labels it: rolling, stable, unmaintained or beta. |
branch | The nixpkgs branch a channel is published from: release-YY.MM for a release series, and for a rolling channel the branch of the same name, which nixpkgs advances to each published revision. |
The current stable release is the highest series among the primary channels and the previous one is the next below it, whatever their status: for a month after a release both read stable, and the status is carried so a reader sees which.
Cached for fifteen minutes, against a half-hourly reading. When no reading is held, because the poll has not run yet or has been failing since the last one expired, the route infers the channel list from the release calendar and four GitHub branch lookups instead, as it always did. It says so: basis: "inferred", source: "nixpkgs-github", commitDate in place of revision and status, and an observation block with the reason and the last failed poll when there is one. A GitHub failure on that path answers 500 with success: false, never a stale channel presented as current.
Channel history
GET /api/v1/nix/channels/history?channel=nixos-unstableEvery change this service has observed to one channel's revision or status, newest first. Nothing upstream keeps this: Prometheus holds the series for its retention window and channels.nixos.org shows the present revision only, so the record begins when this service began observing.
{
"success": true,
"data": {
"channel": "nixos-unstable",
"source": "prometheus.nixos.org",
"entries": [
{ "channel": "nixos-unstable", "kind": "revision", "from": "e158d9ed9b51c98974c5e66e1ba1c9e0255fecaa", "to": "0b7a2d9c4e6f8a1b3c5d7e9f0a2b4c6d8e0f1a2b", "observedAt": "2026-09-29T04:00:03.917Z", "status": "rolling", "previousStatus": "rolling", "snapshot": "9a8b7c6d5e4f30211a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f7081" },
{ "channel": "nixos-unstable", "kind": "appeared", "from": null, "to": "e158d9ed9b51c98974c5e66e1ba1c9e0255fecaa", "observedAt": "2026-09-28T00:00:03.551Z", "status": "rolling", "snapshot": "4f9c1e7a3b2d8e6f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071" }
],
"count": 2,
"limit": 50,
"nextBefore": null,
"retained": { "held": 2, "cap": 1000, "dropped": 0, "oldestHeld": "2026-09-28T00:00:03.551Z", "recordedSince": "2026-09-28T00:00:03.551Z" },
"note": "Changes since observation began, at each half-hourly poll. from and to are nixpkgs revisions; kind is revision, status, appeared or retired. dropped entries beyond the cap remain in the archived snapshots."
},
"timestamp": "2026-09-29T09:12:40.210Z"
}| Parameter | Meaning |
|---|---|
channel | Required. The name as NixOS publishes it. Without one the answer is 400 with code CHANNEL_REQUIRED and the channels known. |
limit | Entries per page, 1 to 200, default 50. |
before | The nextBefore of the previous page. Entries observed strictly before it are returned. |
kind | Meaning |
|---|---|
revision | The channel advanced. from and to are the revisions either side. |
status | The same revision under a new status, as when a release goes out of support and reads unmaintained. previousStatus carries the old one. |
appeared | The channel was seen for the first time, including every channel at the first observation. from is null. |
retired | The channel is no longer reported. to is null. |
observedAt is when this service noticed, at a half-hourly poll, not when the channel advanced. snapshot is the digest of the archived channel set the change was seen in, and each archived snapshot names the one before it, so the chain of snapshots is the full record even once the working copy has dropped entries beyond its cap; retained.dropped counts those.
A channel in the latest observation with no recorded change answers 200 with an empty page. A name never observed is 404 with code CHANNEL_UNKNOWN. A history that could not be read is 503 with code HISTORY_UNAVAILABLE, which says nothing about the channel.
Fix status per channel for a CVE
GET /api/v1/cve/:id/nix/channelsThe nixpkgs pull requests whose text names the CVE, and for each channel whether one of them has reached it.
{
"success": true,
"data": {
"cveId": "CVE-2024-3094",
"fixPRs": [
{ "number": 300028, "title": "Revert \"xz: 5.4.6 -> 5.6.1\"", "state": "closed", "url": "https://github.com/NixOS/nixpkgs/pull/300028", "createdAt": "2024-03-29T16:20:25Z" }
],
"channelStatus": {
"unstable": { "fixed": true, "pr": { "number": 300028, "title": "Revert \"xz: 5.4.6 -> 5.6.1\"", "state": "closed", "url": "https://github.com/NixOS/nixpkgs/pull/300028", "createdAt": "2024-03-29T16:20:25Z" } },
"26.05": { "fixed": false, "pr": null },
"25.11": { "fixed": false, "pr": null }
}
},
"timestamp": "2026-09-28T01:04:06.748Z"
}fixPRs is a text search: a pull request that mentions the CVE is evidence that somebody connected the two, not a fix claim. fixed: false for a stable channel means no mentioning PR was found to have reached it. For a dated answer about one specific commit, use fix timing, which asks you to name the commit and records that the attribution is yours.
Backports of a pull request
GET /api/v1/nix/pr/:number/backportsWhether the pull request merged and which release branches carry a backport of it. A backport is a separate commit with its own sha, so use the mergeCommit under each branch, not the original, when asking a containment question.
{
"success": true,
"data": {
"merged": true,
"prNumber": "300000",
"mergedAt": "2024-04-01T20:04:00Z",
"mergeCommit": "b399a09e3b4dc7ef23505513d39dd2c0dcf6b842",
"targetBranch": "master",
"title": "python312Packages.threadpoolctl: 3.3.0 -> 3.4.0",
"backports": {}
},
"timestamp": "2026-09-28T01:03:04.292Z"
}An empty backports object is a statement that no backport was found, which for a pull request merged only to master is the ordinary answer.
Stable channel containment
GET /api/v1/nix/stable/:series/containment/:shaWhen a commit reached a published nixos-<series> channel release: the first release whose revision contains it and the last one that did not, so the window is visible. There is no data envelope.
{
"series": "25.05",
"commit": "29e290002bff",
"outcome": "landed",
"landedIn": { "release": "nixos-25.05.5210.f1a7c8d9e2b3", "revision": "f1a7c8d9e2b3...", "publishedAt": "2025-08-12T03:14:00Z" },
"lastUncontainedRelease": { "release": "nixos-25.05.5188.0c2e4b6a8d1f", "revision": "0c2e4b6a8d1f...", "publishedAt": null },
"checkedAgainst": { "latestRelease": "nixos-25.05.6012.9b8a7c6d5e4f", "publishedAt": "2026-09-27T22:40:00Z" },
"note": null,
"checkedAt": "2026-09-28T01:03:10.000Z"
}| Outcome | Meaning |
|---|---|
landed | A published release of the series contains the commit. landedIn names it. |
not_landed | No published release contains this exact commit. Cherry-picks get new shas, so a fix merged to master and backported under a different sha reads not_landed here; supply the commit that landed on the release branch. |
commit_unknown | GitHub does not know the sha. |
series_unknown | No channel history is published for that series. |
release_history_unresolvable | The published history names a revision GitHub cannot resolve, so containment cannot be computed. A property of the channel history rather than an outage; retrying will not change it. |
An upstream failure answers 502 naming the source. The route never answers 200 with an invented outcome.
Hydra build status
GET /api/v1/nix/hydra/:packageThe latest Hydra builds of one job in the nixpkgs unstable jobset, grouped by platform. The path segment is the package attribute as Hydra names the job (curl, python3Packages.requests), not a channel name.
{
"success": true,
"data": {
"found": true,
"package": "curl",
"platforms": {
"x86_64-linux": { "latestBuild": 312345678, "buildStatus": 0, "timestamp": 1758999600, "hydraUrl": "https://hydra.nixos.org/build/312345678" }
}
},
"timestamp": "2026-09-28T01:02:58.877Z"
}buildStatus is Hydra's own code: 0 is a successful build. found: false means Hydra had no matching job, which is not a failed build. Hydra not answering is 502 with data: null, never found: false. Cached for four hours.
Related
- Fix timing: when a named fix commit reached each supported stable channel.
- Nix CVEs: the open nixpkgs security issues with their linked pull requests.
- Repology lookup: the versions each nixpkgs channel carries.