· 5 min read ·

The Package Manager Arms Race and What It Costs the Rest of Us

Source: simonwillison

Simon Willison recently made an argument that package managers need to cool down, and the timing feels right. We are in a period where the tooling layer underneath software projects is moving faster than the projects themselves, and that inversion has consequences worth naming clearly.

The past three years have been genuinely productive for package management. uv, released by Astral in early 2024, solved real problems in the Python ecosystem: installation speed, dependency resolution time, and reproducibility across environments. Bun did the same on the JavaScript side, bundling a runtime, package manager, and bundler into a single binary and benchmarking dramatically faster than npm or even pnpm. Cargo remains a model the rest of the ecosystem keeps trying to replicate. These are not frivolous projects.

But the pace has not slowed down after those wins. It has accelerated. And acceleration at this layer has a particular character: it compounds friction for everyone downstream.

The Fragmentation Problem in Python

Python is the clearest example. The current Python packaging landscape includes pip, pip-tools, pipenv, poetry, pdm, hatch, conda, mamba, and uv, all competing for the same space with overlapping but not identical feature sets. Each has a legitimate reason to exist at the moment it was built. pip-tools addressed repeatable builds before pip had lockfiles. Poetry addressed the setup.py / setup.cfg split that made project metadata painful to manage. uv addressed the fact that everything before it was slow by a large margin.

The problem is not that each tool existed. The problem is the lack of sunsetting. Every new tool adds documentation overhead, CI/CD configuration decisions, and onboarding questions without retiring the tool it supersedes. A new contributor to a Python project today has to answer a question that would have been nonsensical in 2018: which of these six package managers is this project using, and which subset of their features am I actually supposed to be familiar with?

PEP 517 and PEP 518 standardized the build system interface, and PEP 621 standardized project metadata in pyproject.toml. These were genuine improvements that were supposed to consolidate the ecosystem. They did consolidate some of it. But they also created a new surface for competing implementations to differentiate themselves on features rather than correctness.

The JavaScript Situation Is Older and Arguably Worse

The JavaScript ecosystem went through this cycle earlier. npm shipped in 2010. Yarn launched in 2016, directly from Facebook, with a detailed blog post explaining why npm was insufficient: deterministic installs, offline caching, parallel fetching. Those were real problems, and Yarn solved them.

Then npm caught up. npm 5 shipped a lockfile. npm 7 shipped workspaces. By the time npm had addressed most of what made Yarn necessary, pnpm had arrived with a different model entirely: a content-addressable store that hard-links packages across projects, saving disk space and installation time. Bun later collapsed the distinction between runtime and package manager.

Each of these tools is technically interesting. The content-addressable store in pnpm is a clever solution to a real problem. But consider what this progression means for a project that started in 2016: it may have used Yarn, migrated to npm when Yarn 2 changed its model in ways that broke existing workflows, and is now being asked to consider Bun. Each migration carries risk and costs CI time to validate.

The churn is not hypothetical. Yarn’s release of Berry (v2) in 2020 broke enough existing workflows that many teams stayed on Yarn Classic (v1) indefinitely, and the Yarn 1 repository is now in maintenance mode while large projects refused to migrate. That is the kind of cost that comes from competing on features before finishing the previous round of stabilization.

Security Surface and Tooling Velocity

There is a security dimension here that deserves attention separate from developer experience. Package managers are trusted executors. They download code and run it, or make it available to tools that run it. The more implementations exist in active use, the more surface area there is for supply chain attacks.

The xz backdoor in March 2024 was a social engineering attack against a compression library, not a package manager. But it demonstrated how patient adversaries can target the build and distribution toolchain at its seams. Package managers are prominent seams. Every new tool that achieves wide adoption adds a new installation path, a new update mechanism, and a new set of maintainers to potentially target.

When uv asks you to run curl -LsSf https://astral.sh/uv/install.sh | sh, that is a trust decision as much as a convenience decision. Astral has a good reputation and the tool is open source. But the pattern of bootstrapping package managers by piping scripts to the shell is an industry habit that treats security as someone else’s problem. It is worth noticing that this pattern has not meaningfully changed even as the tools have gotten faster and more sophisticated.

What Healthy Consolidation Looks Like

Cargo is worth examining as a counterexample. The Rust ecosystem has one package manager, and it ships with the toolchain. Cargo handles building, testing, benchmarking, documentation generation, and dependency resolution in a single tool. It is not perfect. The compile times that Cargo orchestrates are notoriously slow. Workspace support has had rough edges. But the ecosystem has one answer to the question “how do I install and manage dependencies,” and that answer has remained stable for years.

The result is that Rust documentation does not need to hedge. Every tutorial, every example project, every README assumes Cargo. This is underappreciated as an advantage. Documentation that does not have to explain prerequisite tooling choices teaches its actual subject faster.

Go achieved something similar with its module system after a painful transition from GOPATH. The initial rollout of Go modules was contentious, and there was a period where the community was split between the old and new approaches. But the Go team committed to the migration and provided clear timelines. The ecosystem is now effectively unified on modules.

The Actual Request

The case for slowing down is not a case against improvement. uv is better than pip in ways that matter. Bun is faster than npm in ways that matter. The case is for finishing what gets started before adding the next thing.

Specifically: tools that achieve wide adoption carry a maintenance obligation that compounds with time. That obligation includes security patches, but also behavioral stability and migration tooling for users who built workflows on your previous version. The package manager ecosystem has been good at shipping the new version and less good at supporting the transition or acknowledging when the prior version should be retired.

For developers building on top of these tools, the practical advice is conservative: treat your choice of package manager as a long-lived architectural decision, not a performance optimization. The benchmarks that justify switching from one tool to another are usually measuring cold-cache scenarios on clean installs, not the integration cost of migrating an existing project with custom scripts, CI configuration, and team habits built around the old tool.

The innovation is welcome. The expectation that users should keep pace with it is less welcome, and that expectation is currently implicit in how the ecosystem operates.

Was this interesting?