lukeed

Maintaining 300 packages alone

Name

maintaining-alone — maintaining 300 packages alone

Synopsis

How one person keeps a few hundred packages alive. What I triage, what I ignore, what I automate, and what agents changed.

Filed

· 1,334 words · 11 min

Description

An npm search for packages I maintain returns 348 results. This site tracks 281 of them. My GitHub account has 345 public repositories, and in September the packages on this site were downloaded about 3.6 billion times.

There is no team. There is me, some evenings, and a lot of CI.

I already wrote about why small packages are the only kind I can afford to maintain. This post is about the other half: the actual work. What I look at, what I do not, and what changed when agents showed up.

#Most packages need nothing

The first thing to accept is that most of the list is quiet.

A package that parses argv or finds a file in a parent directory does not need a release every month. The invisible packages are, by design, almost done. dequal has not had a release since 2022. That is not neglect. That is the package working.

So I do not think of maintenance as "300 projects". I think of it as a few active projects, a few dozen that wake up when the platform moves under them, and a long tail that needs nothing until somebody finds a real bug.

The platform does move, though. Node drops a version. A new runtime appears. ESM changes the rules for every package at once. Those are the weeks when the quiet packages are not quiet, and I wrote about the worst of them in the ESM transition.

#Triage

Issues are where the time goes. uvu has 91 open issues right now. polka has 33. clsx has 16.

I sort every new issue into one of four bins, more or less on sight:

  1. A bug with a reproduction. This goes first, always. A small repo or a failing test is the most valuable thing anyone can send me.
  2. A bug without a reproduction. I ask for one. If I can reproduce it myself in a few minutes, I do. If I cannot, it waits.
  3. A feature request. Usually the answer is no, for the reasons in the post on invisible packages. Sometimes the request shows that the README is unclear, and the fix is in the docs, not the code.
  4. A question. I answer it if I can do it in one reply. If the same question comes twice, the README changes.

That last rule does more work than it sounds like. A lot of the issues I get are really documentation bugs. When I fix the docs, the issue stops coming back. When I only answer it, it comes back next month from someone else.

#What I ignore

This is the part people do not like to hear.

I ignore most feature requests on packages that are done. I keep some of them open because closing them feels rude, and I am still not sure that is the right call. An open issue list that never shrinks tells new people the project is struggling, when really it is finished.

I ignore download-count anxiety. A package with a billion downloads a year and two open issues is healthier than a package with a thousand downloads and a roadmap.

I ignore "is this project dead?" issues on packages that have not changed because they did not need to. I do try to answer them once, kindly, because the question is fair. Software that has not changed in two years usually is dead. Mine is just small.

#Release automation

Every release I do by hand is a release I can get wrong. So I am moving packages off hand releases, one at a time.

empathic is the current template. On every push, CI runs deno lint, deno fmt --check, and a type check. Then it builds the npm package and runs the tests on every Node version from 14 to 22. A separate job checks the build size, and another runs the benchmarks. When I push a v* tag, a publish job runs after the checks pass, logs in to npm with OIDC instead of a stored token, and runs npm publish --provenance. A -next. tag publishes to the next dist-tag instead.

That means a release is: change the version, tag it, push. I never type npm publish for it, and there is no token on my machine that could publish it.

The older packages do not work like this yet. clsx, dequal, kleur, and uvu were last published from my laptop. Moving them is slow, boring work, and it is the right work. The CI file is almost the same for each, so most of the effort is checking that the built output did not change.

The stats on this site are automated the same way. A scheduled job pulls stars, versions, and downloads for every package once a day, and commits the result.

#Burnout

I do not have a clean answer here.

What I can say is what the structure does. Small packages with no dependencies have no dependency updates. CI that publishes on a tag means a release costs ten minutes, not an evening. A "no" said early costs less than a "maybe" that turns into a year of open thread. Each of those choices is about making the work survivable on a bad week, not efficient on a good one.

#What agents changed

I run a lot of branches in parallel now, and most of them are not mine. That is why I wrote lane. Agents changed the maintenance work a lot.

What got better.

  • Reproductions. An agent can turn a vague issue into a failing test faster than I can read the thread. Most of the time, the test is wrong in an interesting way, and that is still faster.
  • Platform sweeps. "Update the CI matrix in 40 repos" used to be a weekend. Now it is a prompt and a review.
  • Old code. Some of these packages are nine years old. An agent will read the history and explain why a line is there before I change it. I do not always trust the answer, but it is a good place to start.
  • Docs. Agents read READMEs literally, which makes them very good at finding where the docs and the code disagree. That is a big reason I care more than ever about writing the README first.

What got worse.

  • Volume. Producing an issue or a pull request now costs almost nothing. Reviewing one costs the same as it always did. That gap lands on maintainers.
  • Confident and wrong. A generated PR often looks finished. It has tests, a description, and a changelog line. Sometimes it fixes a problem the package does not have, or changes a behavior that half the ecosystem depends on.
  • My own review load. Agents I run produce work too, and I still have to read all of it. The bottleneck moved from writing to reviewing, and reviewing is the part of the job I am slowest at.

My rule now: an agent can do anything up to the merge. The merge, and the tag, are mine. If a change goes out to a few hundred million installs, a person should have read it.

I also write packages differently because of this. Small, explicit APIs with good examples are easier for agents to use correctly, which means fewer confused issues downstream. I wrote more about that in designing APIs for agents and humans.

#Alone is a choice

I could add co-maintainers to more of these.

For most of the list, though, alone works because the packages are small enough to hold in one head. The day a package needs a second person to understand it, it has probably grown past what it should be.

That is the real trick, as far as I can tell. I do not maintain 300 packages. I maintain 300 small things that mostly maintain themselves, and I try very hard not to let any of them become big.

See also

open-source(7), maintenance(7), agents(7), npm(7), lukeed(1)