depbisect
v0.1.5
Published
git bisect, but for dependency updates — find the minimal set of dependency changes (npm, pnpm, yarn, Cargo, Go, uv, Composer, pip) between two commits that breaks your build, and prove it's minimal.
Maintainers
Readme
You merge a PR that bumps 40 dependencies. CI goes red. Which bump broke it?
git bisect walks commits — DepBisect bisects the dependency changes themselves: it diffs the direct dependencies in your manifest (package.json, Cargo.toml, go.mod, pyproject.toml, composer.json, or requirements.txt) between two revisions, then narrows them to the exact minimal subset that makes your command fail — and proves no smaller set does. Every install runs in a throwaway git worktree, so it never touches your checkout.

Why DepBisect
Reach for it when a dependency-update PR (Dependabot, Renovate, or a manual bump) turns CI red and you can't tell which bump did it — especially when dozens changed at once, or when the culprit only breaks in combination with another bump.
| Instead of… | The catch | DepBisect |
| ------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
| git bisect | Bisects commits — useless when the break is inside one dependency-bump commit | Bisects the dependency changes themselves |
| Reverting bumps by hand | O(n) reinstalls, easy to miss interacting deps, no proof you found the real cause | ddmin + an automatic 1-minimality proof |
| Reading the lockfile diff | Shows what resolved differently, not what broke | Pinpoints the exact minimal breaking set |
| npm why / npm ls | Explains the dependency tree, not the failure | Ties the failure to specific version bumps |
How it works
DepBisect diffs the direct dependencies declared in your manifest between two revisions, then runs Zeller's ddmin delta-debugging algorithm to find the smallest failing subset — followed by a one-by-one removal pass that certifies the result is 1-minimal (removing any single package from the set makes the failure stop reproducing).
Install
npm install -g depbisect
# or: pnpm add -g depbisect
# …or run without installing:
npx depbisect run --base origin/main -- npm testThis package ships a prebuilt binary for your platform as an optional dependency. There is no Go toolchain to install, no post-install script, and no network fetch at install time — just the binary for your os/cpu.
Quick start
cd your-repo # your tests fail after a dependency bump
# Preview which dependencies changed (installs nothing):
depbisect run --base origin/main --dry-run -- npm test
# Bisect, re-running each candidate 3x to absorb flaky tests:
depbisect run --base origin/main --runs 3 -- npm test
# Interrupted? Resume where you left off:
depbisect run --base origin/main --runs 3 --resume -- npm test
Example output
$ depbisect run --base origin/main --runs 3 -- npm test
baseline all reverted → pass (3/3)
baseline all applied → fail (3/3)
ddmin 12 changes → 6 → 3 → 5 → 4 → …
reproduced → 5 packages
minimality removing eslint-plugin-react → pass ✓
minimality removing @typescript-eslint/parser → pass ✓
minimality removing webpack → pass ✓
minimality removing react-scripts → pass ✓
minimality removing jest-environment-jsdom → pass ✓
result 1-minimal failing set (5 of 12 changes)
react-scripts 4.0.3 → 5.0.1
jest-environment-jsdom 27.5.1 → 29.7.0
webpack 5.75.0 → 5.97.1
@typescript-eslint/parser 5.62.0 → 7.18.0
eslint-plugin-react 7.31.2 → 7.37.2
report depbisect-report.md
report depbisect-report.jsonSupported ecosystems
| Manager | Manifest | Lockfile |
| ------- | ---------------- | ------------------------------- |
| npm | package.json | package-lock.json (v1–v3) |
| pnpm | package.json | pnpm-lock.yaml (v5/v6/v9) |
| yarn | package.json | yarn.lock (classic + Berry) |
| cargo | Cargo.toml | Cargo.lock |
| go | go.mod | go.sum |
| uv | pyproject.toml | uv.lock |
| composer | composer.json | composer.lock |
| pip | requirements.txt | requirements.txt (exact == pins) |
Auto-detected from the manifest, or force one with --pm <npm|pnpm|yarn|cargo|go|uv|composer|pip>.
Parallel bisection with --jobs
Candidate subsets are independent — DepBisect can evaluate them across several isolated worktrees at once. Same minimal result at any job count; only the wall time changes.
Sequential (--jobs 1):

Parallel (--jobs 12):

Resume after interrupt
Press Ctrl-C mid-bisection and DepBisect checkpoints completed trials to disk. Re-run with --resume to restore them instead of starting over.

Common flags
| Flag | Description |
| ----------------------- | ----------------------------------------------------------------------------- |
| --base <rev> | Base revision to compare against (required) |
| --to <rev> | Target revision (default HEAD) |
| --runs <n> | Verification runs per candidate; raises confidence on flaky tests (default 1) |
| --jobs / -j <n> | Evaluate candidates in parallel across isolated worktrees (default 1) |
| --dry-run | Show detected changes and plan, then exit without bisecting |
| --resume | Resume completed trials from the checkpoint |
| --quiet / --verbose | Print only the final result / stream all subprocess output |
| --pm <manager> | Force a package manager (default: auto-detected) |
Exit codes
| Code | Meaning |
| ---- | ---------------------------------------------------------- |
| 0 | Minimal failing set found |
| 1 | Usage or runtime error |
| 2 | Failure did not reproduce with all updates applied |
| 3 | Command fails even with all updates reverted |
| 4 | Inconclusive: flaky verification or minimality unprovable |
| 5 | No direct dependency changes between the revisions |
GitHub Action
- uses: skyneticist/depbisect@v0
with:
base: ${{ github.event.pull_request.base.sha }}
command: npm test
runs: "3"Safety
- Your checkout is never modified — all installs happen in a DepBisect-owned temporary worktree.
git reset --hardandgit clean -ffdxrun only inside that worktree.- The worktree is removed and pruned afterward, even on Ctrl-C.
- Arguments are preserved exactly — no shell evaluation unless you invoke one.
Installing dependencies runs their lifecycle scripts, exactly as a manual
npm installwould. Bisecting untrusted version ranges therefore executes untrusted code — run it in the same sandbox you'd trust fornpm installof those versions.
More
Licensed under the MIT License.
