Skip to content
Engineering

How to Fix npm Breaking Changes Automatically

Ovvoc team··15 min read

Breaking changes are the #1 reason dependency updates fail. When a package removes an API, renames a method, or changes its module system, your build breaks — and tools like Dependabot and Renovate can’t help because they only bump versions. Ovvoc is the first tool that automatically fixes breaking changes in npm packages using AST transforms and AI, covering all 25 categories of dependency updates from simple version bumps to complex paradigm shifts.

Why breaking changes are the #1 dependency problem

The numbers paint a stark picture. According to Snyk’s 2023 State of Open Source Security report, 84% of codebases contain at least one known vulnerability in their dependencies. GitHub’s Octoverse data shows that over 80% of dependencies go un-upgraded for more than a year. The primary reason is not ignorance or laziness. It is fear of breaking changes.

“Breaking changes are why teams stop updating. Not because updates are hard to find, but because they’re hard to survive.”

The pattern is the same across thousands of teams. A tool like Dependabot opens a pull request bumping express from 4.18.2 to 5.0.1. CI runs. Tests fail. The PR sits untouched for weeks, eventually lost in a backlog of 47 other stale dependency PRs. Developers have learned through painful experience that merging a major version bump without careful code review is asking for a production outage.

This is how dependency debt accumulates. Not all at once, but one ignored PR at a time. A study by the University of Southern California found that teams spend 14+ engineering hours per sprint dealing with dependency-related issues — from security patches that need manual code changes to major version migrations that touch dozens of files. That is time not spent building features, fixing bugs, or shipping product.

The root cause is simple: existing tools detect what needs updating but cannot fix the code changes required by the update. They automate the easy part (finding new versions) and leave you with the hard part (making your code work with them).

What are breaking changes? The 25 types

Not all dependency updates are created equal. Ovvoc classifies every npm package update into one of 25 categories, organized into three tiers by the complexity of code changes required. This taxonomy is unique to ovvoc and forms the foundation of its transform engine.

Tier 1: No code change needed (Categories 1–6)

These are the updates that every tool can handle. The version number changes in package.json, the lockfile updates, and your code continues to work without modification.

  • Category 1 — Patch version bump: Bug fixes within the same minor version (e.g., 4.18.1 → 4.18.2)
  • Category 2 — Lockfile-only update: Transitive dependency resolution changes
  • Category 3 — Type definition update: Changes to @types/* packages
  • Category 4 — Security patch: Known vulnerability fixes within a compatible range
  • Category 5 — DevDependency update: Build tool or test utility version bumps
  • Category 6 — Optional dependency update: Non-critical peer or optional dependency changes

Tier 2: Simple code transforms (Categories 7–13)

This is where most tools fall off the cliff. The update requires changes to your application code, but the changes follow a deterministic pattern that can be expressed as a rule.

  • Category 7 — Function rename: oldName()newName()
  • Category 8 — Import path change: from 'old/path'from 'new/path'
  • Category 9 — Parameter order change: Arguments swapped or added in function signatures
  • Category 10 — Config format change: Configuration keys renamed or restructured
  • Category 11 — Deprecated API replacement: Removed APIs replaced with new equivalents
  • Category 12 — Default value change: Behavior shifts from changed defaults
  • Category 13 — Type signature change: TypeScript type updates for changed interfaces

Tier 3: Complex transforms (Categories 14–25)

These are the updates that every developer dreads. They involve fundamental architectural changes that touch many files and require understanding the intent of the code, not just its structure.

  • Category 14 — Paradigm shift: Class components → hooks, callbacks → async/await
  • Category 15 — Architecture change: Pages Router → App Router, Express middleware redesign
  • Category 16 — Module system change: CommonJS → ES Modules
  • Category 17 — Hidden behavior change: Same API, different runtime behavior
  • Category 18 — Coordinated multi-package update: Several packages must update together
  • Category 19 — Build tool migration: Webpack → Vite, Babel → SWC
  • Category 20 — Test framework change: Jest → Vitest, Mocha assertion changes
  • Category 21 — CSS/styling change: Tailwind v3 → v4, CSS Modules changes
  • Category 22 — Database driver change: Query builder or ORM API changes
  • Category 23 — Authentication/security change: Auth library API overhaul
  • Category 24 — Deployment config change: Container or CI/CD configuration changes
  • Category 25 — Custom/novel change: Unique breaking changes with no established pattern

Most tools handle Tier 1. Nobody handles Tier 2 and 3. Ovvoc handles all 25 categories. Read the full category documentation for detailed descriptions of each type.

Current tools and their limits

Before explaining how ovvoc solves the problem, it is worth understanding what the current ecosystem offers — and where each tool stops short. Every tool below solves a piece of the puzzle. None solve it end to end.

npm audit fix

Built into npm since version 6, npm audit fix scans your dependency tree for known vulnerabilities and attempts to install compatible patched versions. It handles Tier 1 security patches well but has zero capability to modify application code. If a security fix requires a major version bump with breaking changes, npm audit fix either skips it or bumps the version and hopes for the best. It addresses roughly 6 of 25 categories.

npm-check-updates (ncu)

The ncu tool updates version ranges in package.json to the latest available versions. Its --doctor mode is clever: it runs your tests after each update and rolls back if they fail. But it does not fix anything. It tells you the update broke your tests — information you already had. The --doctor mode takes 10–30 minutes per package because it runs the full test suite for every version bump, and at the end you still need to fix the failures manually.

patch-package

When a dependency has a bug or incompatibility, patch-package lets you modify the code inside node_modules and save the diff as a patch file. It is a workaround, not a solution. Patches accumulate over time — we have seen projects with 40+ patch files that nobody understands or dares to remove. Each patch is a ticking time bomb that may silently conflict with future updates.

Dependabot and Renovate

These are the most popular dependency automation tools, used by millions of repositories. Both detect available updates and open pull requests with version bumps. Both are excellent at Tier 1 updates. Neither modifies a single line of application code. When CI fails on a Dependabot PR, the PR sits there gathering dust. Renovate offers automerge for non-breaking updates, but when a major version introduces breaking changes, Renovate is just as helpless. See our detailed Dependabot comparison for a side-by-side analysis.

Codemods (jscodeshift, @codemod/cli)

Codemods are scripts that transform code using AST manipulation — conceptually similar to what ovvoc does. The difference is that codemods require someone to write and maintain the transformation script for every migration. React publishes codemods for major version upgrades. Next.js has a codemod CLI. But for the 2+ million packages on npm, only a handful publish official codemods. You cannot write a codemod for every package update in your dependency tree. It does not scale.

All these tools handle pieces of the problem. None solve it end to end. Ovvoc is the first tool that combines version detection, code transformation, build verification, and test validation into a single automated pipeline.

How AST transforms fix breaking changes automatically

The core technology behind ovvoc’s code transformation engine is Abstract Syntax Tree (AST) manipulation. Instead of treating your code as a flat string of characters and using regex-based find-and-replace (which breaks on edge cases), ovvoc parses your code into its structural representation and makes targeted, precise modifications.

What is an AST?

An Abstract Syntax Tree is the structured representation of source code that every compiler and interpreter uses internally. When you write app.get('/users', handler), the parser does not see 30 characters. It sees a tree of nodes: a CallExpression with a MemberExpression callee (app.get), a StringLiteral first argument ('/users'), and an Identifier second argument (handler). Each node has a type, properties, and position information.

This structure is what allows ovvoc to make surgical modifications. When a breaking change renames app.del() to app.delete(), ovvoc does not search for the string “del” in your code. It walks the syntax tree, finds CallExpression nodes where the callee is a MemberExpression with the property del on an Express application object, and rewrites only those nodes. Variables named del, strings containing “delete”, and comments mentioning deletion are never touched.

OXC: the fastest JavaScript parser

Ovvoc uses OXC (Oxidation Compiler), the fastest JavaScript and TypeScript parser available. Written in Rust, OXC parses code 5–10x faster than Babel and 3–5x faster than SWC. It produces a full-fidelity AST with parent node access, semantic analysis, and native TypeScript support without requiring type-checking. Speed matters because ovvoc processes entire repositories. A monorepo with 500 files completes its full scan in milliseconds, not seconds. Learn more in our deep dive on AST transforms.

Pattern matching and rewriting

Each transform rule defines a pattern (what to find) and a replacement (what to produce). Here is a real example from the Express 4 to 5 migration:

Express 4 (before)
- app.del('/user/:id', deleteUser);
- app.get('/*', catchAll);
- app.get('/files/:name?', getFile);
Express 5 (after transform)
+ app.delete('/user/:id', deleteUser);
+ app.get('{*path}', catchAll);
+ app.get('/files{/:name}', getFile);

Three different breaking changes, three different AST transforms, all applied in a single pass. The method rename finds MemberExpression nodes with property del. The wildcard rewrite finds StringLiteral nodes used as route parameters containing /*. The optional parameter rewrite detects :param? syntax in route strings. Each transform operates independently, targets only its specific pattern, and produces deterministic output.

AST transforms are deterministic — they always produce the correct output for a given input. No guessing, no hallucination, no probabilistic uncertainty. The same input file will always produce the same output file, which makes the results fully reproducible and auditable.

11 transform types, 190+ rules

Ovvoc ships with 11 categories of AST transforms covering function renames, method renames, import path changes, named export renames, default-to-named import conversions, parameter reordering, wrapper additions, property access changes, config key migrations, Express wildcard rewrites, and Express optional parameter rewrites. These 11 types are codified into 190+ rules across 35 packages including Express, React, Next.js, ESLint, Tailwind, TypeScript, Vite, esbuild, Babel, lodash, and more.

When AI steps in — categories 14–25

Deterministic AST transforms handle categories 1–13 with perfect accuracy. But for paradigm shifts, architecture changes, and other complex migrations, pattern matching alone is not enough. Converting a React class component to a functional component with hooks requires understanding lifecycle methods, state management, and side effects — context that cannot be captured in a simple find-and-replace rule.

For these cases, ovvoc uses narrowly-scoped AI: specific input, specific task, validated output. The AI does not write arbitrary code. It receives the exact file that needs transformation, the specific migration guide for the package version change, and examples of the correct transformation pattern. Its output is constrained and validated.

Consider a React class component that needs to become a functional component with hooks:

React class component (before)
- class UserProfile extends React.Component {
- state = { user: null, loading: true };
- componentDidMount() {
- fetchUser(this.props.id).then(user =>
- this.setState({ user, loading: false })
- );
- }
- render() {
- if (this.state.loading) return <Spinner />;
- return <Profile user={this.state.user} />;
- }
- }
Functional component with hooks (after)
+ function UserProfile({ id }) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ useEffect(() => {
+ fetchUser(id).then(user => {
+ setUser(user);
+ setLoading(false);
+ });
+ }, [id]);
+ if (loading) return <Spinner />;
+ return <Profile user={user} />;
+ }

This transformation requires understanding that componentDidMount maps to useEffect with a dependency array, that this.state maps to individual useState calls, and that this.props becomes destructured function parameters. Ovvoc’s AI handles this with a budget-controlled approach: maximum tokens per transform and a circuit breaker that stops after repeated failures to prevent runaway costs.

AI handles the 30% of changes that deterministic rules cannot. But it is always validated by the build and test pipeline. If the AI produces incorrect output, the build fails, and no PR is opened — you get a failure report instead. The AI is a tool in the pipeline, not the pipeline itself.

The full pipeline: from detection to verified PR

Ovvoc’s pipeline runs 10 stages for every dependency update. Each stage has a clear responsibility, and the pipeline stops immediately if any stage fails. No broken code ever reaches your repository.

  1. Monitor: Watch the npm registry for new versions of your tracked dependencies. Ovvoc checks every 6 hours using the npm registry API, comparing your current versions against the latest available.
  2. Detect: Compare current vs. latest version, classify the bump type (patch, minor, major), and check the changelog for breaking changes. Ovvoc fetches migration guides from three sources: npm package metadata, GitHub Releases, and web search.
  3. Plan: Categorize each change into one of the 25 categories and select the appropriate transform strategy — deterministic AST transform, AI-assisted migration, or version bump only.
  4. Clone: Spin up an ephemeral Docker container with network isolation, clone your repository, and install dependencies. The container is destroyed after the job completes — your code never persists on ovvoc’s infrastructure.
  5. Update: Bump the version in package.json and regenerate the lockfile using your project’s package manager (npm, yarn, pnpm, bun, or yarn berry).
  6. Transform: Apply AST transforms and AI migrations for all breaking changes identified in the planning stage. Multiple transforms can run in a single pass.
  7. Build: Run your project’s build command. If the build fails, the pipeline stops and reports the failure.
  8. Test: Run your project’s test suite. Every test must pass. If any test fails, ovvoc attempts AI-assisted diagnosis and repair (up to 3 retries).
  9. PR: If build and tests pass, open a pull request with a detailed description listing every change made, the category classification, and the transform strategy used.
  10. Report: If anything fails at any stage, send a detailed failure report explaining what changed and what needs manual attention. No broken PRs, ever.

The entire pipeline runs in 49 seconds to 5 minutes per update, depending on repository size and test suite complexity. Compare that to the 2–4 hours a developer spends on a manual major version migration.

Real example — Express 4 to 5 in 49 seconds

Here is a real pipeline run from ovvoc’s public showcase, demonstrating an end-to-end Express migration:

Pipeline Run — [email protected] → 5.0.1
Stage 1  — Detect     ✓  express 4.18.2 → 5.0.1 (major bump, 15 breaking changes)
Stage 2  — Plan       ✓  8 AST transforms needed (Categories 7, 8, 10, 11)
Stage 3  — Clone      ✓  Repository cloned in isolated container
Stage 4  — Install    ✓  npm install completed (1,247 packages)
Stage 5  — Update     ✓  express bumped to 5.0.1 in package.json
Stage 6  — Transform  ✓  8 transforms applied in 2.1 seconds
Stage 7  — Build      ✓  Build PASSED
Stage 8  — Test       ✓  43/43 tests PASSED
Stage 9  — PR         ✓  Pull request #127 opened
Stage 10 — Report     ✓  Success notification sent

Total time: 49 seconds
Files modified: 12
Transforms applied: 8 (wildcard routes, optional params, method renames,
                       deprecated middleware, path syntax, error handling)

Eight breaking changes across 12 files, all detected, planned, transformed, built, tested, and shipped as a verified pull request in under a minute. The same migration takes a developer 2–4 hours manually — reading the Express 5 migration guide, finding every affected file, making the changes, running the tests, fixing failures, and iterating until everything passes.

The PR description includes every change made, the category classification for each transform, and links to the relevant sections of the Express 5 migration guide. Your code reviewer sees exactly what changed and why. Read our Express 4 to 5 migration guide for the full technical breakdown of every transform applied.

Frequently asked questions

Can ovvoc fix ALL breaking changes?

Ovvoc handles all 25 categories of npm dependency updates. Categories 1–13 are fully deterministic — AST transforms produce correct output every time. Categories 14–25 use AI-assisted migration with build and test validation. For truly novel breaking changes with no established pattern (Category 25), ovvoc sends a detailed failure report with guidance on what changed and what needs manual attention. In ovvoc’s public showcase challenge, 24 out of 30 open-source projects received successful automated PRs.

What if the build fails after transforms?

No pull request is opened. Ovvoc follows a strict policy: if the build fails or any test fails after 3 AI-assisted repair attempts, you receive a detailed failure report instead of a broken PR. The report includes the exact error messages, the transforms that were applied, and guidance on what needs manual attention. Your main branch is never affected. Learn more about this approach on our zero-breakage guarantee page.

Does ovvoc work with TypeScript?

Yes. OXC parses TypeScript natively — including generics, type annotations, enums, interfaces, and JSX/TSX. All 11 AST transform types work on both JavaScript and TypeScript files. Type signature changes (Category 13) are handled specifically, updating interfaces, type parameters, and generic constraints to match the new API.

How is this different from codemods?

Codemods require someone to write and maintain transformation scripts for each specific migration. When React publishes a new major version, the React team writes a codemod for that one migration. Ovvoc ships with 190+ pre-built rules across 35 packages and adds new rules continuously. More importantly, ovvoc handles the entire pipeline — detection, transformation, building, testing, and PR creation — not just the code transformation step. Codemods are a screwdriver. Ovvoc is the entire toolbox.

What packages does ovvoc support?

Ovvoc has custom transform rules for 35 packages including Express, React, Next.js, ESLint, Tailwind CSS, TypeScript, Vite, esbuild, Babel, lodash, Axios, Jest, Mocha, Webpack, and more. Any npm package gets version bumps and build verification (Tier 1 categories). Packages with custom rules get full code transformation (Tiers 2 and 3). Visit the migration rules documentation for the complete list.

Stop babysitting your dependencies.

Let ovvoc fix the breaking changes while you ship features. See plans and pricing →

Stay up to date

Automate your dependency updates. Start with one repo.