The Stalled-Dependency Verdict: Hold, Vendor, Fork, or Migrate
You can take one dependency that has stopped moving and produce a defensible verdict backed by its blast radius, exit cost, and forkability.
Key takeaways
- The two-year release gap is a near-hard line: only 6.6% of Maven libraries ever have a gap that long, so waiting past it has a documented failure rate.
- Explicit end-of-life notices drive downstream removal about 1.58x faster than silent abandonment, so a 'hold' verdict should be tighter for a silently-dead package.
- Migration cost is partly a hiring-pool question: in Refolk's index the US senior/staff Go pool (6,483) is 5.31x deeper than Rust (1,221), making a mainstream target cheaper to move onto.
- Fork-and-maintain is a staffing bet against a scarce pool: only 116 US and 42 German 'open source maintainer' profiles surface in Refolk's index, so bus factor gates the fork verdict.
- Overrides change only resolved lockfile versions while SBOMs still report the old one, so a vendor-and-pin verdict can stop CI failures yet fail a dependency-inventory audit.
- Most exposed downstream projects never remove an abandoned dependency, so the default organizational outcome is silent accumulation the framework exists to interrupt.
A dependency your code relies on has gone quiet, and you need to decide what to do before it becomes a fire. This guide is for engineering managers, tech leads, and technical founders who already depend on something that is now dying, and it scores the exit decision itself rather than helping you vet a new library. By the end you can take one stalled dependency and produce a defensible verdict - hold and monitor, vendor and pin, fork and maintain, or migrate off - backed by its blast radius, exit cost, and forkability.
Most guides stop at adoption. This one starts after adoption, when the maintainer has drifted away and the default organizational outcome is to do nothing. That default is the risk: the npm-scale study found most exposed projects never remove or replace an abandoned dependency. The framework's whole value is forcing an explicit verdict instead of drift.
Is this dependency actually stalled, or just finished?
A stalled dependency is one that has stopped receiving human maintenance while you still depend on it. A finished dependency looks identical on a commit graph but is stable, low-complexity, and needs no updates. Separating the two is the first judgement call, and getting it wrong in either direction is expensive.
There is no single numeric cutoff for "dead." CHAOSS, the community that publishes open-source health metrics, deliberately avoids cross-project comparison because every project is different. What it offers instead is a proxy: its internal norm is that every pull request should receive a response from a human within two business days. Time to First Response - the gap between when an issue or change request is opened and its first human reply - is the load-bearing signal.
The strongest numeric threshold comes from release cadence. Two proxies matter, and they disagree on purpose:
| Proxy | What it flags | How to read it |
|---|---|---|
| No release in 12 months | Weak abandonment signal | Overestimates; some low-complexity packages are simply finished |
| Release gap over 2 years | Near-hard line | Only 6.6% of libraries ever have a gap this long |
The two-year line is the one to trust. Only 6.6% of Maven libraries have a release gap over two years, which makes reactivation past that point highly unlikely. The 12-month proxy is noisier: about 61% of npm packages and 57% of PyPI packages had no release in the last 12 months, and many of those are healthy and complete, not dead.
The base rates are worth keeping in view, because they set your prior before you look at any single package.
| Ecosystem | Proxy | Abandoned share |
|---|---|---|
| npm | No release in 12 months | 61% |
| PyPI | No release in 12 months | 57% |
| npm (widely-used) | 6-year window | 15% |
Read the last row carefully. Among widely-used npm packages, 15% were abandoned within six years, and 45% of open-source core developers completely disengage from a project for at least one year. Abandonment is not an edge case. It is the ambient weather of the dependency graph.
One more trap on this step: bot noise. Dependabot and CI activity can influence Time to First Response and Change Request Closure Ratio, so a repo that looks responsive may only have automation talking to itself. Confirm a human is answering before you call a package alive.
What does the EOL signal change about your urgency?
An explicit end-of-life or deprecation notice changes the urgency of your verdict, not just its wording. When a maintainer publicly announces abandonment, downstream projects remove the dependency about 1.58x faster than they do for silent abandonment. That number cuts two ways, and both matter for your call.
If the package is loudly deprecated, the community around you will act. Substitutes and forks appear faster because the volunteer's dilemma - community members can invest in shared fixes but often do not, because of competing demands on their time - resolves faster when abandonment is public. Your hold window can be slightly longer, because help is more likely to arrive.
If the package is silently dead, tighten your hold verdict. Nobody is announcing, so nobody around you is moving, so a community-accepted alternative may never coalesce. The npm study found that mature project governance, disciplined dependency management, and an explicit public announcement all make removal more likely. Absent those, silent accumulation is the norm and you are on your own timeline.
Record a clean yes or no on the EOL question before you spend real effort. It is the cheapest lever in the whole space.
How do I measure blast radius and exit cost?
Blast radius is how much of your code touches the dependency and how deep it sits in your tree; exit cost is the effort to leave by each available route. No single published standard exists for either, so you build the estimate from reusable proxies rather than a named method.
Three proxies carry the blast-radius estimate:
- Call sites and used API surface. Count where you actually call the library and which of its functions you use. This is almost always smaller than the whole library, and it is the number migration effort scales with.
- Transitive closure size. Resolve the full tree from the lockfile with
npm lsandnpm explain. The gap between declared and resolved is large: htmx's 13 development dependencies balloon into 411 dependencies once resolved, around 110 MB of node_modules. - Upstream-dependency fraction. The fraction of used packages defined in upstream repositories is a coupling proxy. Projects with more upstream dependencies face more breaking changes, so a dependency that drags in many others raises your exposure even if your own call sites are few.
Building a blast-radius estimate
- Count call sitesGrep the codebase for import and call points to the package
- List used APIRecord only the functions and types you actually call
- Resolve the treeRun npm ls or npm explain to get the transitive closure
- Score couplingNote the upstream fraction as a breaking-change proxy
Exit cost then splits by verdict, and the cost drivers are documented even though no closed-form model is. Migration effort scales with the used API surface and transitive coupling above. Fork carry cost is ongoing: a fork must stay in sync with unrelated upstream changes, and GitHub forks have issues, Actions, Dependabot, and security features disabled by default, so a fork needs manual setup - a lot of infrastructure for a two-line fix. A patch is lighter but changes what the lockfile entry means: you run code that does not match the recorded version, and SBOMs generated from the lockfile list the unpatched version. Sponsoring upstream avoids all local carry cost, but only works if a maintainer still exists to fund.
Migration cost is also partly a hiring-pool question, which is easy to forget when you stare only at code. The same call-site count is a cheaper migration onto a mainstream target with a deep labor market than onto a niche one.
| Skill | Country | Senior/staff profiles | Depth vs Rust |
|---|---|---|---|
| Rust | United States | 1,221 | 1.00x |
| Go | United States | 6,483 | 5.31x |
In Refolk's index of professional profiles, the US senior/staff Go pool is 6,483 against 1,221 for Rust, a 5.31x deeper pool. Migrating onto a mainstream target is not just a code decision; it is a bet on whether you can staff the result. When your candidate replacement and your target labor market are both unclear, Refolk lets you size the pool of engineers who have actually shipped against your migration target before you commit to it.
How do I score whether this is a good fork candidate?
Forkability turns on two gates: bus factor and license. Pass both and a fork is on the table; fail either and it is not, regardless of how much you would rather not migrate.
Bus factor, sometimes called truck factor, is the minimum number of developers who need to leave a project for it to stall. A low count means concentrated, hard-to-reproduce knowledge: in the worst case only one person holds the key knowledge of important components. That is exactly the knowledge a fork does not inherit. If you fork a repo whose real expertise left with a departed author, you have adopted the maintenance burden without the maintenance ability.
License is the second gate. The MIT License is the most permissive and popular, allowing you to use, copy, modify, distribute, and sell, requiring only that you include the original copyright notice. Apache 2.0 is also permissive and adds an explicit patent grant that protects you from patent lawsuits from contributors. If the license does not permit redistribution of a modified copy, the fork verdict is off the board before you start.
The fork-viability call
Fork-and-maintain is fundamentally a staffing bet against a scarce pool. In Refolk's index, only 116 US and 42 German professionals surface for "open source maintainer."
| Segment | Country | Matching profiles | vs US |
|---|---|---|---|
| Open source maintainer | United States | 116 | 1.00x |
| Open source maintainer | Germany | 42 | 0.36x |
That scarcity is why bus factor gates the fork verdict. A fork you cannot staff is a slow re-abandonment, and you will be back at this decision in a year with less patience. Beyond the two gates, confirm a clean git history and a reproducible build, because a fork you cannot build from source is not a fork you can maintain.
What interim mechanism stops the bleeding while I decide?
Before you commit to any verdict, apply a pin, an override, or a patch to stop CI failures and buy decision time. These are interim moves that close the immediate window without closing the underlying ticket, and each ecosystem has a native mechanism.
Overrides force the resolver to use the version you specify everywhere in the tree, regardless of what any dependency declares. Your declared dependencies do not change; only the resolved versions in the lockfile do. Support is broad:
- npm and pnpm use an
overridesblock; npm from 8.3 and up, pnpm from 7 and up. - Yarn uses
resolutions- the same mechanism, only the key differs; Classic v1 and Berry v2 and up. - Gradle uses
dependencySubstitution, a rule triggered for every dependency including transitive ones when the configuration resolves, and composite builds automatically substitute external dependencies with local forks. - Maven has no single overrides field; control comes from
dependencyManagementin the pom to force a transitive version. - Go uses
gohack, which checks out a module's source locally and adds thereplacedirective to go.mod in one command. - npm patches via patch-package let you check patches into version control so teammates and CI reapply them, modifying dependencies without the burden of forking.
{
"overrides": {
"stalled-subdep": "1.4.2"
}
}
# Verify it actually took effect:
npm ls stalled-subdep
# Every resolved line should now read 1.4.2. If any other version appears,
# the override key is mis-nested or version-qualified and is silently no-opping.Replace the package name and version with yours. Confirm it resolved with the ls check below; an override that never applies is worse than none.
The interim move is not the verdict. An override that fixes red CI does not update your dependency inventory, and a patch does not change what your SBOM reports. Treat these as the tourniquet, then write the actual decision.
The stalled-dependency procedure
Run these seven steps in order on the one dependency in front of you. The output is a one-page verdict a reviewer can challenge, justified by blast radius, exit cost, and forkability.
From "it went quiet" to a defensible verdict
- Confirm it is stalled, not finishedPull last-commit age, last-release age, median time to first response, and open-issue trend, then verdict against the two-year and 12-month release proxies. Guard against the feature-complete misread by checking whether the package is low-complexity and dependency-light.
- Check for an explicit EOL or deprecation noticeLook for a deprecation flag, archived repo, or README end-of-life statement and record yes or no. An explicit notice raises urgency and tightens your hold window, because peers remove it about 1.58x faster.
- Measure blast radiusCount call sites and the API surface you actually use, then trace the transitive closure via the lockfile and npm ls or npm explain. Produce a call-site count and a used-API list.
- Estimate exit cost per optionDerive migration effort from the used surface, fork carry cost from the disabled-by-default fork infrastructure, and patch or override feasibility from your package manager. Produce a rough hour estimate for each verdict.
- Score forkabilityCompute or estimate the bus factor, check the license permits redistribution, and confirm a clean history and reproducible build. Produce a fork-viable yes or no.
- Pick the interim override mechanismApply a pin, override, or checked-in patch to stop the bleeding while you decide, and confirm green CI on the pinned version with npm ls verifying the override resolved.
- Write the verdictChoose hold, vendor-and-pin, fork-and-maintain, or migrate, justified by blast radius, exit cost, and forkability. Produce a one-page decision with the trigger that would flip it.
The four verdicts map cleanly onto what the earlier steps produced. Hold and monitor when blast radius is small, the package is finished rather than dead, and there is no CVE pressure. Vendor and pin when you need to freeze a known-good version fast, using an override or patch, while you keep deciding. Fork and maintain when the used surface is large, migration is expensive, and both fork gates pass and you can staff it. Migrate off when the used surface is manageable, a mainstream target with a deep hiring pool exists, or forkability fails.
The default outcome is silent accumulation; the verdict exists to interrupt it before it becomes a fire.
How this goes wrong: failure modes and false positives
The costliest errors in this decision are false positives - calling something dead that is finished, or calling an override safe that silently no-ops. Each has a specific check that catches it before it reaches a review.
| Failure mode | What it looks like | The check |
|---|---|---|
| Feature-complete misread as dead | 18 months no commits, zero CVEs, flagged abandoned | Is it low-complexity and dependency-light? Then it may just be finished |
| Star-count reassurance | High stars read as maintained | Check last-commit date; gitflow has 26k stars and an 11-year-old last commit |
| Override that silently no-ops | Override added but never applies | npm ls shows only the overridden version everywhere |
| Patch that breaks the SBOM | Fixed locally, audit still reports vulnerable | Lockfile and SBOM still list the unpatched version |
| Fork underestimated | Fork for one patch, inherit CI and security setup | Confirm forks have Actions and Dependabot disabled by default |
| Bot noise inflates responsiveness | Dependabot activity read as human care | Automation can influence Time to First Response; find a human reply |
Two more deserve their own weight. First, low bus factor ignored when forking: forking a repo whose real knowledge sat with one departed author gives you the code without the ability to maintain it. Check the truck-factor concentration before you commit, not after. Second, pinning treated as free safety: pinning direct dependencies is often assumed strictly safer, but it increases the cost of maintaining vulnerable dependencies and can even raise exposure to malicious updates in large graphs.
The through-line across every failure mode is the same: the signals that reassure you - stars, green CI, bot activity, a clean pin - are the ones most likely to lie. Trust dated facts and resolved trees over comfortable proxies.
Keeping the verdict current
A verdict is a snapshot, so write down the trigger that would flip it and the date you will re-check. A "hold and monitor" on a silently-dead package should carry a shorter re-check interval than one on a loudly-deprecated package, because the community around a silent death moves slower and you inherit their exposure.
Before you call the verdict done
- The stall is confirmed against the two-year and 12-month release proxies with a dated line
- The EOL question is answered yes or no and the hold window reflects it
- A call-site count and used-API list exist, not a vague "we use it a lot"
- An hour estimate exists for each of hold, vendor, fork, and migrate
- Fork viability is a yes or no with bus factor and license both checked
- The interim override is live, CI is green, and npm ls confirms it resolved
- The SBOM gap from any patch or override is noted, not silently carried
- The verdict names the single trigger that would change it and a re-check date
Re-run the procedure when the trigger fires: a new CVE against the package, an upstream archive or deprecation notice, a resolved-tree change from an unrelated upgrade, or a shift in your migration target's hiring pool. For the fork and migrate verdicts specifically, the staffing question is the one that ages fastest, because the maintainer pool is thin - 116 US and 42 German profiles in Refolk's index - and the depth of your migration target's talent market is what turns a defensible plan into an executable one. Re-size that pool before you green-light the work, not after you have committed a quarter to it.
The point of scoring this call is not to make forking or migrating feel safe. It is to make the decision explicit, dated, and challengeable, so that the most common outcome - doing nothing while exposure quietly accumulates - stops being the one that happens by default.
Questions practitioners ask
How do I know if a dependency is actually abandoned or just finished?
Check the release gap against two proxies. No release in 12 months is a weak signal that overestimates abandonment, because some low-complexity packages are simply complete. A release gap over two years is a near-hard line: only 6.6% of Maven libraries ever have a gap that long, and reactivation past it is highly unlikely. Separately, confirm the package is not just low-complexity and dependency-light, which is what a genuinely finished library looks like.
When should I fork a dependency instead of migrating off it?
Fork when the bus factor and license both pass and your used API surface is large enough that migration is expensive, but you can staff the ongoing sync. A fork must stay in sync with unrelated upstream changes, and GitHub forks have issues, Actions, Dependabot, and security features disabled by default. If you cannot commit maintainer time, a fork becomes a slow re-abandonment. Migrate instead when a mainstream replacement exists and your target has a deep hiring pool.
Does pinning or an override fix an abandoned dependency?
No. An override changes only the resolved versions in your lockfile, not your declared dependencies, so it stops CI failures without closing the underlying ticket. It also does not update your SBOM, which still lists the unpatched version and can fail a dependency-inventory audit. Pinning is not free safety either: it raises the cost of maintaining vulnerable dependencies and can increase exposure to malicious updates in large graphs. Treat both as interim, not as the verdict.
What makes a good fork candidate versus a bad one?
Two gates. First, bus factor: the minimum number of developers who need to leave for the project to stall. A low count means concentrated, hard-to-reproduce knowledge, which a fork inherits. Second, license permissiveness: MIT allows use, copy, modify, distribute, and sell with only a copyright notice, and Apache 2.0 adds an explicit patent grant. A permissive license and a bus factor above one are the minimum bar before you commit.
How do I estimate the blast radius of a dependency without a published standard?
No single named method exists, so use three proxies. Count call sites and the actual API surface you use, not the whole library. Trace the transitive closure via the lockfile with npm ls or npm explain, remembering that htmx's 13 dev dependencies resolve to 411 total. Finally, treat the fraction of used packages defined upstream as a coupling proxy, since projects with more upstream dependencies face more breaking changes.
Try it on the search you came here for
Stop building boolean strings. Just describe the person.
Type one sentence. I plan the search, read GitHub, public LinkedIn and Crunchbase records, and the open web as it is right now, and hand back a ranked list with the reason next to every name.
01Describe them
One plain sentence. Role, city, stack, stage, whatever matters to you.
02I read the web live
GitHub, public LinkedIn and Crunchbase records, the open web. Not a database that went stale last quarter.
03You read the shortlist
Ranked, with the reasoning under every name. Open a profile, ask a follow-up, narrow it down.
- Staff backend engineers in NYC who shipped Rust in production
- Series A fintechs in SF under 50 people, growing headcount this year
- Maintainers of fast-growing Rust web frameworks on GitHub
- No boolean, no filters, no seat to buy. One box.
- Read at search time, so a profile updated yesterday counts today.
- Every step visible as it runs, every name with its reason.
500 free credits on sign-up. No card, no demo call. See real searches.