Maintainer Mode
Sign in with GitHub

Maintainer Mode

What should a nixpkgs maintainer do next, for what we give back?

Three queues answer it: pull requests to review, pull requests ready to merge, and packages I watch. A fourth page is your review record. nixfresh answers the same question from the other end, for what we consume: what is stale, what has an advisory, who maintains it. nixfresh stays a read-only public dataset. Maintainer Mode is the only one of the two with accounts.

Review someone else's nixpkgs pull request, and yours gets on the list.

Reviews are cheap and merges are scarce. An approval from someone without commit access moves nothing, so a club that only trades reviews builds a nicer waiting room. The product here is the ready-to-merge queue: pull requests that carry a review that changed something, pass their checks, have no conflict, and have an approval on their current head. A committer can work it from the top down. The review queue exists to feed it.

Maintainer Mode reads GitHub. It never writes to GitHub: no reviews, no merges, no comments, no labels.

Maintainer Mode grants nothing

nixpkgs committers grant commit access. Maintainer Mode hands them evidence they do not have today, and a human still decides.

Getting a pull request listed is a cheap prize, which is why additive-only credit and no dispute process are enough. Commit access is an expensive prize: the moment a number buys it, the incentive to game it outgrows what this design can hold. So nothing here computes a recommendation, and no number unlocks anything beyond a place in the queues.

The record answers: here are this person's last fifty reviews, what came of each, and whose work they overlap with. Its headline is pull requests a committer later merged without further changes after their review. Nothing else shows as directly that someone's judgement already matches the people who hold the merge button, and it cannot be farmed, because a committer has to act for it to count. The second number is problems found: the author changed the pull request after a review that requested changes or commented on a changed line.

There is no leaderboard. A leaderboard drives volume, and volume is the enemy of review quality. A global ranking lets five people dominate while the rest disengage. The visible unit is the outcome on one person's own page, not a count of reviews and not a rank.

Within the nixpkgs rules

Maintainer Mode works inside the nixpkgs automation/AI policy and does not add rules of its own on top of it.

The rules

1. Credit is earned by moving a pull request, not by having an opinion

A review earns credit when it is on someone else's pull request and at least one of these follows within 14 days:

TriggerThe GitHub fact
The author pusheda commit on the head, or a force push, after the review
The author replieda comment by the author, or a reply in a review thread, after the review
Inline on a changed linethe review carries an inline comment whose line is a + or - line of the diff
It mergedthe pull request merged after the review

Every trigger is a fact a program reads; nothing needs a judge. Three of the four need the other party to act, so one person cannot farm them alone.

What this rejects. A bare "LGTM", an approval with no comment, an approval on a pull request that then sits unmerged, a comment on an unchanged context line, a review by a bot, and any review of your own pull request.

Reviewing your own pull request never counts.

2. Whose pull requests appear

A pull request appears in either queue when its author is listed: their reviews earned credit on pull requests of at least three different authors in the last 60 days. A pair trading reviews cannot reach three. A ring big enough to reach it looks like a healthy review community, and it is one.

The age limit is the window: a review older than 60 days no longer counts towards being listed. Its credit also decays (below), so old help fades rather than vanishes.

3. How the ledger is computed

The ledger is a pure function of public GitHub data and the clock (src/ledger.rs). Nothing in it is entered by hand. Delete the database, sync again, and you get the same ledger. Every credit links to the review that earned it.

The constants, and why

ConstantValueWhy
Move window (N)14 dLong enough for a volunteer author to answer over two weekends. Longer, and an unrelated push or merge starts to buy credit.
Decay half-life30 dOne month: a person who stops reviewing drops to a quarter in two months. Shorter punishes a holiday; longer lets the list lag behind who helps now.
Window60 dTwo half-lives: credit older than that is at 25% and no longer says much. It also bounds the first sync of nixpkgs to a size one token can pull.
Distinct authors to list3The smallest number a pair cannot reach. Higher shuts out people who know a small corner of nixpkgs well.
Daily cap5A careful nixpkgs review takes 15 to 60 minutes. Five is a heavy but honest day; twenty in an hour is spraying.
Per pull request1More rounds on one change are the same piece of work. Without this, ten "nit" reviews on one pull request beat one useful review.
Pair factor1/2Halving lets a real working pair still count for something, while the sum over any number of repeats stays below 2.
Queue head age14 dMost nixpkgs pull requests get their first attention, if ever, in the first two weeks. Past that they are the backlog the club exists to drain.
Fresh-pick factor1/2People keep the freedom to review what they understand, and it still pays; the backlog pays double.

GET /api/v1/status returns the live values.

Routing: shown by competence, paid by outcome

What a person is shown is filtered to their area. What earns credit is still only whether the review moved the pull request, so a review outside your area counts when it moves something; you are simply not asked for it.

Your area is derived, never self-assessed. It is the union of:

  1. meta.maintainers: every package whose evaluated meta.maintainers names your GitHub login, at the directory of its meta.position. Teams and computed lists count, because the evaluator resolves them.
  2. ci/OWNERS: the file the "Check / owners" job reads. Team entries are skipped, because team membership is not public.
  3. What you changed: the directories of files in your merged pull requests over the last year, read with your own token when you log in.

On top of that you can add tags, path prefixes such as pkgs/development/python-modules, for what you just started caring about.

A pull request that no member's area covers is unowned. It gets its own bucket, which is the newcomer path: packaging hygiene (complete meta, pinned hashes, platforms, a missing versionCheckHook) needs no domain knowledge and is most of what a first review finds.

Packages I watch

Follow a package, and the watch page shows its open advisories, a newer upstream release, and the open nixpkgs pull requests against it.

The state of nixpkgs

/state and GET /api/v1/metrics show how pull requests flow through nixpkgs, month by month. Every figure is an aggregate. No figure names a person, in line with "no leaderboard".

The full sync reads only pull requests updated in the last 60 days and all open ones. A second walk, the history walk, reads every pull request created since --history-since (MAINTAINER_MODE_HISTORY_SINCE, default 2020-01-01). It reads 50 pull requests per query with fewer fields: the first 10 reviews, the first 50 files, the labels and the last commit message, but not the description. It stores them in a table of their own, so they never reach the ledger. It walks 100 pages per sync pass and resumes from its cursor. The first walk back to 2020 takes several hours. Until it ends, older months are incomplete. The backlog before --history-since is a floor, because a pull request closed before the walk started is not in it.

Three audiences

AudienceSurface
Humansthe web page: ready to merge, needs review (your area, unowned, all), packages I watch, one review record per person
AgentsGET /api/v1/queue/merge, /api/v1/queue/review?bucket=…&login=…, /api/v1/people/<login> (record, credits, area), /api/v1/metrics, /api/v1/status
Programsmaintainer-mode check <login>: exit 0 listed, 3 not listed, 1 broken
maintainer-mode --server https://<host> merge
maintainer-mode --server https://<host> review --bucket mine --login <you>
maintainer-mode --server https://<host> record <login>
maintainer-mode --server https://<host> check <login>; echo $?
maintainer-mode --server https://<host> metrics --months 24

Every query command takes --json.

How the data is pulled

Known limits of slice one

Run it

nix build
GITHUB_TOKEN=… ./result/bin/maintainer-mode sync --db club.sqlite --index
./result/bin/maintainer-mode serve --db club.sqlite
VariableMeaning
MAINTAINER_MODE_LISTENlisten address, default 127.0.0.1:8790
MAINTAINER_MODE_DBSQLite path
MAINTAINER_MODE_PUBLIC_URLthe public URL; the OAuth callback is <url>/auth/callback
GITHUB_CLIENT_IDOAuth app client id
GITHUB_CLIENT_SECRET_FILEfile holding the OAuth app client secret
MAINTAINER_MODE_SYNC_TOKEN_FILEoptional token owned by the deployment
MAINTAINER_MODE_NIXFRESH_URLnixfresh instance for package health on the watch page

CI is the flake's checks: nix flake check.

What we took, and under which licence

PackageVersionLicenceUsed for
@primer/css22.3.2MITdist/primer.css, served as-is
@primer/octicons19.40.0MITthirteen 16px SVG icons
nixfresh nix.rs and attrs.rs2026-10MITsrc/eval.rs and src/attrs.rs: the evaluator wrapper and the attribute index, copied and trimmed

Both are fixed-output fetches of the npm tarballs, pinned by their sha512 (nix/primer.nix), and their licence files ship with the assets. The pages are server-rendered HTML; there is no JavaScript.

Version 22 of @primer/css no longer ships Box, Label, UnderlineNav, Subhead or flash. src/club.css rebuilds those few on Primer's own color tokens, so light and dark mode follow the bundle.

Primer is GitHub's design system, used so that people who arrive from a pull request page do not learn a second interface. The club is an independent community project. It is not a GitHub product and is not endorsed by GitHub, and it does not use GitHub's name or logo as its own.

Licence

MIT