Maintaining wait-on at agent speed: AI, npm supply-chain risk, and a Rust engine A Rust implementation of the wait-on npm package was built in four days by parallel coding agents working overnight, passing the same test suite as the JavaScript version with 496 passing, 0 failing, and 0 pending tests, according to new maintainer Kevin Old. The opt-in Rust engine sits behind the same API as the JavaScript one but is not included in the 10.0 release candidate and is planned for a future major version; Old spent five or six hours planning and steering the work. wait-on, created by Jeff Barczewski in July 2015, recorded 12,791,721 npm downloads in the week of September 24–30 and is pinned at exactly 9.1.0 by start-server-and-test (2,410,323 downloads that week). - Published on Maintaining wait-on at agent speed: AI, npm supply-chain risk, and a Rust engine - Authors - Name - Kevin Old - @kevinold https://twitter.com/kevinold I recently joined Jeff Barczewski https://x.com/jeffbski , who created wait-on https://github.com/jeffbski/wait-on in 2015, as a maintainer of the project. Jeff and I met in the food line at JSConf US in 2013 and have been friends ever since. We've spoken at and attended as many tech conferences together as we can, at least one a year. Working on Jeff's project together is a new chapter of that. On Monday, a Rust implementation of wait-on was a "some day" idea. By Thursday morning there was one. Coding agents had worked overnight, in parallel. They followed Compound Engineering plans and a parent issue that broke the port into lanes: one issue, one plan and one pull request each. The opt-in Rust engine sits behind the same API as the JavaScript one and passes the same test suite: 496 passing, 0 failing, 0 pending. It isn't in the 10.0 release candidate; it's planned for a future major. I spent five or six hours that week planning and steering. It's a side project. This was my first week as a maintainer, and I'm still a little surprised by it. I use the same agent tooling and process in my role as Director of Engineering at Dorvie https://dorvie.com and on side projects. Watching it hold up on software this many people depend on was humbling, and I don't take it as proof of anything about me. It mostly says the process works when the guardrails are good. A new maintainer on a popular package usually spends months reading old code and old issues before daring to change much. I wanted to set a direction and move the project there carefully but fast. The question: can work at that speed be trusted in a package where accuracy is the whole job? What I joined, and why accuracy matters wait-on started with Jeff's first commit in July 2015 and lives at jeffbski/wait-on https://github.com/jeffbski/wait-on . It came with a backlog: old issues, community pull requests, and a dependency tree that had grown with it. Changes start on my fork https://github.com/kevinold/wait-on and go upstream as pull requests. wait-on blocks until a file, port, socket or URL is ready. Usually that means "wait for the dev server, then run the end-to-end tests." npm https://www.npmjs.com/package/wait-on counted 12,791,721 downloads in the week of September 24–30. start-server-and-test https://github.com/bahmutov/start-server-and-test , with 2,410,323 downloads that week, pins it at exactly 9.1.0 . jest-dev-server https://github.com/argos-ci/jest-puppeteer/tree/main/packages/jest-dev-server , with 339,927, detects a timeout by checking that the error message starts with "Timed out waiting for" . It also shows up in the build and test tooling of projects many developers use every day. A GitHub search of large organizations finds wait-on declared in a package.json at Grafana https://github.com/grafana/grafana/blob/main/package.json , WordPress Gutenberg https://github.com/WordPress/gutenberg/blob/trunk/package.json and wordpress-develop https://github.com/WordPress/wordpress-develop/blob/trunk/package.json , Storybook https://github.com/storybookjs/storybook/blob/next/code/package.json , React Router https://github.com/remix-run/react-router/blob/main/integration/package.json , Node.js's own undici https://github.com/nodejs/undici/blob/main/benchmarks/package.json , Cloudflare https://github.com/cloudflare/ai/blob/main/package.json , Elastic https://github.com/elastic/apm-agent-nodejs/blob/main/package.json , Shopify https://github.com/Shopify/js-buy-sdk/blob/main/package.json , Angular https://github.com/angular/dev-infra/blob/main/bazel/package.json , Microsoft https://github.com/microsoft/hve-core/blob/main/docs/docusaurus/package.json , Meta https://github.com/facebook/CacheLib/blob/main/website/package.json , Spotify https://github.com/spotify/lighthouse-audit-service/blob/master/package.json , IBM https://github.com/IBM/eval-assist/blob/main/package.json and Automattic https://github.com/Automattic/simplenote-electron/blob/trunk/package.json . The versions they pin run from 2.x to 9.x, so whatever ships next has to respect years of behavior. So a wrong change here turns into a hung CI job or a false green build for someone who never heard of wait-on. That's the bar any AI-assisted work here has to clear. What a week looked like Between September 22 and October 1: - 27 pull requests merged into jeffbski/wait-on https://github.com/jeffbski/wait-on/pulls?q=is%3Apr+is%3Amerged , 26 of them mine. Jeff reviewed and merged 24 of those. - 17 upstream issues closed. The oldest was opened in 2017. - Six releases https://github.com/jeffbski/wait-on/releases , 9.2.0 through 9.5.1, on September 28 and 29. The release before that, 9.1.0, was in July. 9.x added TypeScript types, a command: resource, --status-codes , a repeatable --header flag, IPv6 tcp, and fail-fast validation of malformed resources. - 10.0.0-rc.1 https://github.com/jeffbski/wait-on/releases/tag/v10.0.0-rc.1 on the next tag on September 30. It removes axios and lodash, moves HTTP checks to fetch with undici, and requires Node 22.19. - 14 lanes of the Rust port merged on my fork, 243 commits on the experimental Rust branch PR 51 https://github.com/kevinold/wait-on/pull/51 . A lot of it wasn't new. Community pull requests had been waiting upstream for years: parseArgs 218 https://github.com/jeffbski/wait-on/pull/218 , TypeScript definitions 197 https://github.com/jeffbski/wait-on/pull/197 , the command resource 88 https://github.com/jeffbski/wait-on/pull/88 , string options 61 https://github.com/jeffbski/wait-on/pull/61 , and two attempts to replace axios with fetch 196 https://github.com/jeffbski/wait-on/pull/196 , 177 https://github.com/jeffbski/wait-on/pull/177 . The agents rebuilt each one as a small, tested PR on today's code. The 10.0 release notes credit Arthur Zamarin @arthurzam https://github.com/arthurzam and @rogervila https://github.com/rogervila as co-authors. How the work runs I run everything through Compound Engineering https://github.com/EveryInc/compound-engineering-plugin , the plugin from Every. overdrive https://github.com/kevinold/overdrive , the harness I put together, installs it. Compound Engineering is a loop: 1. Brainstorm ce-brainstorm turns a vague idea into requirements. 2. Plan ce-plan writes a plan to docs/plans/ https://github.com/kevinold/wait-on/tree/spike-next-rs/docs/plans before anyone writes code. The repo has 47 of them now. 3. Work ce-work implements from the plan, test-first. 4. Review ce-code-review checks the diff, and ce-commit-push-pr and ce-babysit-pr get it through CI. 5. Compound /ce-compound writes down anything non-obvious the work taught us, in docs/solutions/ https://github.com/kevinold/wait-on/tree/spike-next-rs/docs/solutions . That last step is what makes the next week faster than this one. The folder has notes like "a napi addon's glibc floor follows the runner that built it" a native Node module only runs on Linux systems at least as new as the machine that compiled it and "readiness checks must not block the libuv threadpool." Each of those took a failed run to learn. The next agent session reads them first and starts from the fix. The Rust port added one more layer. A parent issue 35 https://github.com/kevinold/wait-on/issues/35 , which we call the spine, breaks the work into lanes. Each lane is one issue, one plan and one pull request, with a fixed list of files it may touch. That's what let several agents work overnight without stepping on each other. When they did collide on ports or merge commits, that went into docs/solutions/ too. Running the lanes is its own job. ko-multi-worker-pm https://github.com/kevinold/skills/tree/main/skills/ko-multi-worker-pm , one of the skills in kevinold/skills https://github.com/kevinold/skills , turns one agent session into a project manager. Pointed at a spine issue, it takes the lanes in order through gates: a start gate, a pre-merge checklist, and a health check after each merge. Pointed at a backlog, it runs up to three workers in parallel. Each worker runs Compound Engineering's /ce-worktree and /lfg in its own git worktree to take an issue from plan to pull request. The PM answers the workers' permission prompts under a strict policy, reading only the literal command and escalating anything it can't classify, then reclaims finished workers and starts the next one. The workers live in herdr https://github.com/herdrdev/herdr , a terminal workspace manager for coding agents, with one labeled pane per worker. herdr is what makes the overnight build possible: I can see every agent at once, step into any pane, and the PM can drive them all from one place. overdrive doesn't contain much of its own. It wires together other people's tools so any coding-agent tool can install them in one step. herdr and my skills from kevinold/skills https://github.com/kevinold/skills install alongside it and complete the harness. Compound Engineering sits at the core. codebase-memory-mcp https://github.com/DeusData/codebase-memory-mcp gives the agent a code graph to query instead of grepping. context7 https://github.com/upstash/context7 supplies current library docs. caveman https://github.com/JuliusBrussee/caveman and ponytail https://github.com/DietrichGebert/ponytail keep output terse and code minimal. Model "gears" pair a worker model that builds with a stronger model for planning and an optional second model, from a different vendor, that reviews adversarially. Speed made the rules stricter Neither people nor agents get it right every time, and the faster the work went, the more the project needed rules a machine can check. The one that changed most came from a real miss. The axios-to-fetch work 238 https://github.com/jeffbski/wait-on/pull/238 named its riskiest assumption in the plan and verified it in prose. The branch that handles proxies from environment variables dropped strictSSL , ca and cert for HTTPS targets. No test combined an HTTPS target, an env proxy and a TLS option, and a reviewer had to catch it. Since then the repo's AGENTS.md https://github.com/kevinold/wait-on/blob/spike-next-rs/AGENTS.md makes test-first development mandatory. Every bug starts as a failing reproduction. Every risk named in a plan becomes a failing test. When two inputs interact, the plan lists the combinations and each one gets a test. The rest: - One concern per PR , so a person can review each change on its own. - A human merges. Agents open pull requests, and a maintainer decides. - Agents can't edit CI. Rust lanes may not touch .github/workflows/ . They change CI only through npm scripts that call cargo xtask , the repo's Rust build tooling. - Checked commit messages , through a local hook and commitlint in CI. - Nobody publishes from a laptop. semantic-release publishes from CI through npm's trusted publishing OIDC , so there's no stored npm token to steal. Supply-chain defense is why Rust After the Shai-Hulud worm spread through npm packages, the standard npm advice is to install with ignore-scripts=true and keep dependency trees small and audited. wait-on gets installed into nearly every e2e pipeline, so its dependencies are part of a lot of other people's attack surface. The JS releases already cut the tree. wait-on@9.5.1 depends on joi , rxjs , axios and lodash . wait-on@10.0.0-rc.1 depends on joi , rxjs and undici . The Rust port is the next step, and the plan ranks its reasons: supply-chain surface first, then reaching people without Node, then startup, then correctness. A single audited Rust core can take over the HTTP stack and the polling loop, which leaves only joi on the JS side. That goal shaped the packaging, along with a research note Jeff shared on September 28 on distributing native addons through npm: - Same API. waitOn opts , cb , the Promise form, the CLI and the types don't change. - JS stays the default. You opt in with WAIT ON ENGINE=rust . That path never loads rxjs or undici . - No install scripts, no download at install. All eight platform binaries ship in one tarball. A container test installs it with ignore-scripts=true and runs it with --read-only --network none . - Audited crates. cargo-deny and cargo-vet gate the Rust dependencies, because npm provenance proves who built a binary, not that the crates inside it are safe. - Same tests, both engines. The same mocha suites run under both engines with no pending list, and the Rust core is held at 100% line and region coverage. Two engines in one repo The Rust engine isn't a separate rewrite waiting to replace the JS code in one big swap. It's being built side by side with the JavaScript engine in the same repo, in the open, on PR 51 https://github.com/kevinold/wait-on/pull/51 . The Rust code lives in crates/ , next to lib/ . npm test runs the JS engine, and WAIT ON ENGINE=rust-strict runs the same mocha suites against the Rust engine. One package carries both, and the environment variable picks which one runs. That's what makes the eventual cutover boring: once the Rust engine has soaked on the next channel, the default flips from JS to Rust, the JS engine stays one more major as a fallback, and then it goes, taking rxjs and undici with it. Agents read the Rust now DHH made the same bet in the Rails World 2026 opening keynote https://www.youtube.com/watch?v=vDjW dRyKXY on September 23: agents are rebuilding HEY's backend in Rust, and DHH is happy never to read it. I agree. We're porting wait-on to Rust for security and speed. From here on, agents are the main readers of that code. They write it, review it, and pick it up in the next lane. My job is to read the plans and tests, and to make the tests strict enough to trust code I don't read line by line. That's why the guardrails above are machine-checked instead of left to my attention. What it costs - Tarball size. The JS-only package is about 70 KB unpacked. With eight binaries the latest prerelease is 15,035,917 bytes packed, about 36 MB unpacked. On one target, a tuned release profile took the binary from 4.75 MB to 1.61 MB. I'm working toward about 8 MB packed, with a size budget in CI. - One package costs bytes. Per-platform optional dependencies download only your platform's binary, but a lockfile made on one OS can drop the others npm/cli 4828 https://github.com/npm/cli/issues/4828 . We chose one package and pay in size. - The dependency cut hasn't landed. The Rust path doesn't load rxjs or undici , but they're still installed: until the Rust engine becomes the default, the package installs the same 11 runtime packages as the baseline. After that, 8. - No faster startup from the addon. It still boots Node. - Still in progress: a set of plain-language Gherkin tests describing how libraries consume wait-on, run against the installed package under both engines, and a harness that runs start-server-and-test's and jest-dev-server's own tests against the new build. If you want to try this - Try Compound Engineering. npx -y github:kevinold/overdrive sets it up with the rest of the harness. Write the plan first and let /ce-compound keep what you learn. - Run lanes in parallel. Install herdr https://github.com/herdrdev/herdr , then npx skills add kevinold/skills -s ko-multi-worker-pm , and point it at a spine issue. - Test wait-on@next https://www.npmjs.com/package/wait-on?activeTab=versions . If you depend on wait-on, especially its API, install the 10.0 release candidate and report anything that behaves differently on jeffbski/wait-on https://github.com/jeffbski/wait-on/issues . - Copy the guardrails. Test-first, one concern per PR, a human merges, agents can't edit CI, no publishing from laptops. None of it depends on Rust or on any particular agent. On a package this widely installed, moving at agent speed only works when the guardrails can be checked as fast as the code gets written.