In 1973, Horst Rittel and Melvin Webber published “Dilemmas in a General Theory of Planning”, introducing the concept of “wicked problems” in urban planning. These aren’t just hard problems. They’re problems where the act of trying to solve them changes what the problem is. Problems where you can’t test solutions in advance. Problems where every stakeholder has a different definition of success.
Package management fits the definition. I’ve spent years working on package manager data and tooling, and the more I learn, the more the wicked problem framework explains why progress feels so difficult. Tens of millions of packages, hundreds of millions of versions, trillions of downloads. Small improvements at this layer affect every project built on top.
Rittel and Webber identified ten characteristics that distinguish wicked problems from tame ones. Here’s how each one applies to the work of managing software dependencies.
1. No definitive formulation
“Formulating the problem and the solution are essentially the same thing.”
The term itself is ambiguous. Say “package management” and some people think npm or Cargo, others think apt or Homebrew. One is about managing dependencies while building software, the other about installing finished tools and libraries onto a system. Same words, different problems. Even within a single ecosystem, the naming is contested: is the unit a package, a module, a crate, a distribution? These aren’t synonyms. They encode different assumptions about what gets versioned, what gets published, and what gets installed. Package management is naming all the way down, and naming is famously one of the two hard problems in computer science.
Ask ten people what package management should accomplish and you’ll get ten different answers. Should it prioritize reproducibility or freshness? Security or convenience? Should it be centralized for coordination or federated for resilience? The answers shape the problem definition.
When npm added lockfiles, it wasn’t solving a pre-existing problem everyone agreed on. It was redefining what “installing dependencies” meant. Same with Cargo adding a central registry when Go was experimenting with URL-based imports. Each design decision changes what problem you’re trying to solve.
2. No stopping rule
“Since there is no definitive ‘the problem,’ there is also no definitive ‘the solution.’”
When is a package manager done? Bundler has been around since 2010 and is still adding features. npm is on its tenth major version. UV just rewrote Python packaging from scratch, and people are already filing issues for the next iteration.
Work stops when you run out of time or money, when maintainers burn out, or when something newer arrives. Not because the problem got solved. Even apparent stability might just mean the energy moved elsewhere.
3. Good-or-bad rather than true-or-false
“Solutions to wicked problems are not true-or-false, but good-or-bad.”
Solutions can’t be objectively correct, only better or worse from someone’s perspective.
Homebrew’s choice to use git as a database was neither right nor wrong. It worked brilliantly for years, then became a bottleneck. Whether that was a good decision depends on whether you weight early simplicity or long-term scaling. Different stakeholders will evaluate it differently.
Semver was an attempt to make versioning true-or-false: either a release breaks compatibility or it doesn’t. In practice it became good-or-bad anyway. Different ecosystems interpret it differently. Some communities treat it as sacred law, others as rough guidance. The spec’s existence didn’t settle the question. Hyrum’s Law makes it worse: a bug fix for one user is a breaking change for someone who depended on that bug. There’s no objectively correct version bump, only outcomes that help or hurt specific people.
4. No immediate or ultimate test
“Solutions to wicked problems generate waves of consequences.”
When Go chose URL-based imports, the immediate effect seemed elegant: no central registry, no naming conflicts. Years later, the consequences emerged: leftpad-style breakages when repos disappeared, security implications from trusting random hostnames, and the eventual need for a central proxy anyway.
You can’t A/B test a package manager design. By the time you know whether a decision worked, you’ve built an ecosystem on top of it. Even in retrospect, we can’t know if the alternative would have been better. Maybe a central Go registry from day one would have created different, worse problems.
5. Irreversible consequences
“Every implemented solution leaves traces that cannot be undone.”
Once PyPI accepted namespace-less package names, typosquatting became inevitable. You can’t fix it without breaking every existing requirements.txt file. Once npm let packages depend on version ranges, you couldn’t switch to pinned-only without breaking the ecosystem’s culture.
Every registry accumulates cruft that can never be removed. RubyGems still hosts gems that haven’t been updated since 2007. Yanking packages breaks dependent builds. The only direction is forward, with all the baggage included. Even attempts to fix cruft create new cruft: if PyPI added namespaces tomorrow, it would just be another layer on top of the existing flat namespace.
6. No well-described set of solutions
“There is no enumerable set of potential solutions.”
Different stakeholders envision different acceptable outcomes.
Registry operators want stability. Security researchers want auditability. Library authors want flexibility. Maintainers want sustainability. Application developers want reproducibility. Corporate users want compliance. These goals actively conflict. A registry that allows easy package updates is also one that’s easier to attack. A lockfile that captures every transitive dependency is also one that’s harder to update.
The npm/Yarn/pnpm competition is partly a story of different stakeholder priorities winning out. Yarn emerged because Facebook needed reproducibility npm didn’t provide. pnpm emerged because disk space and install speed mattered more to some users than compatibility. Each tool embodies a different answer to “what should a package manager optimize for?” and all three coexist because there’s no consensus.
7. Essential uniqueness
“There are no ‘classes’ of solutions that can be applied to a specific case.”
You might think: npm solved this problem, let’s copy their approach. But npm’s solutions emerged from JavaScript’s specific history, its browser origins, its lack of a standard library, its culture of tiny packages. Cargo’s solutions emerged from Rust’s different history and constraints.
When Python tried to adopt npm-style lockfiles, it didn’t quite work. The Python ecosystem had different expectations about virtualenvs, system packages, and global installs. pip-tools, pipenv, poetry, and uv all took different approaches to the same problem, and the fragmentation itself became a new problem. Python also carries a constraint Node and Rust don’t have: it’s both a dev tool and a critical OS component. Break system Python and you break your operating system.
8. Symptoms of other problems
“Every wicked problem can be considered a symptom of another problem.”
Problems are embedded in other problems.
Supply chain security isn’t just a package management problem. It’s entangled with how open source gets funded, how maintainers get support, how companies evaluate risk, how developers learn security practices. You can’t fix typosquatting without addressing why attackers have economic incentives to do it.
The dependency explosion in modern software is a symptom of developer productivity pressure, which is a symptom of market competition, which is a symptom of capitalism’s relationship with software. Package managers just happen to be the layer where the symptom manifests. Ecosystems evolve toward what they reward, not what they intend.
9. Multiple causal explanations
“The choice of explanation determines the nature of the problem’s resolution.”
Different stakeholders propose competing theories about causes and remedies.
Why are there so many npm security incidents? Some blame JavaScript culture (too many small packages). Some blame npm’s design (too easy to publish). Some blame corporate underinvestment in security. Some blame the fundamental architecture of dynamic imports. Some blame attackers getting smarter.
Each explanation suggests different remedies. If it’s cultural, we need education. If it’s design, we need breaking changes. If it’s investment, we need money. The theories point in different directions, and the same people often hold several of them simultaneously.
10. No right to be wrong
“The planner has no right to be wrong.”
Unlike scientists proposing testable hypotheses, package manager designers must live with their choices.
When left-pad got unpublished, npm couldn’t just say “oops, that was an interesting experiment, let’s try again.” Thousands of builds broke. When npm later changed the unpublishing rules, that wasn’t a hypothesis either. It was a permanent policy that would shape behavior forever.
Package managers operate production infrastructure. You don’t get do-overs. The cost of being wrong is measured in broken builds, security breaches, and lost trust. Not acting is also a choice with consequences. npm’s initial permissiveness around unpublishing shaped everything that came after.
None of this means we shouldn’t try to improve package management. The leverage is enormous: better security defaults would protect millions of projects, better funding mechanisms could support the maintainers everything depends on, better coordination could prevent the next left-pad. But the wicked problem framework explains why progress is slow, why solutions create new problems, and why experts disagree about basics. Package managers will never be “solved.”
Rittel and Webber’s answer to wicked problems was participatory planning: bring stakeholders together early, iterate continuously, accept that you’re managing tradeoffs rather than finding solutions. If we accept that these problems are wicked, we stop looking for a perfect tool and start looking for better ways to communicate across tools. That shift from tools to interfaces is what led me to think about what a shared protocol for package management might look like.
Related reading
- Dilemmas in a General Theory of Planning – Rittel & Webber (1973). The original paper that defined wicked problems.
- The Architecture of Open Source Applications: Python Packaging – Tarek Ziadé. Discusses the challenges of standardizing packaging tools while maintaining backward compatibility.
- Hyrum’s Law – An observation on software interfaces that explains why true-or-false versioning often fails in practice.
- Surviving Software Dependencies – Russ Cox (2019). On managing dependencies at scale and the tradeoffs between different approaches.
- An Empirical Comparison of Dependency Network Evolution in Seven Software Packaging Ecosystems – Decan, Mens, Grosjean (2019). Demonstrates structural differences that complicate cross-ecosystem generalization.
- How to Break an API: Cost Negotiation and Community Values in Three Software Ecosystems – (2016). How different ecosystems handle breaking changes differently.