I use Claude to audit npm dependencies before every major upgrade
← Back
April 4, 2026Claude6 min read

I use Claude to audit npm dependencies before every major upgrade

Published April 4, 20266 min read

Major version upgrades of npm packages used to be something I dreaded. I would run npm outdated, see 40 packages with major updates, and put it off for another quarter. Then I built a workflow that uses Claude to read changelogs and breaking change notes, and generate a prioritized upgrade plan. The audit that used to take a day now takes an hour.

The audit workflow

python
#!/usr/bin/env python3
# dep-audit.py

import subprocess
import json
import anthropic
import requests
from packaging.version import Version

client = anthropic.Anthropic()


def get_outdated_packages() -> list[dict]:
    """Get list of outdated packages with current/latest versions."""
    result = subprocess.run(
        ["npm", "outdated", "--json"],
        capture_output=True, text=True
    )
    if not result.stdout.strip():
        return []
    
    outdated = json.loads(result.stdout)
    packages = []
    
    for name, info in outdated.items():
        current = info.get("current", "0.0.0")
        latest = info.get("latest", current)
        
        try:
            current_v = Version(current)
            latest_v = Version(latest)
            is_major = latest_v.major > current_v.major
        except Exception:
            is_major = False
        
        packages.append({
            "name": name,
            "current": current,
            "latest": latest,
            "is_major_update": is_major,
        })
    
    # Focus on major updates first
    return sorted(packages, key=lambda x: x["is_major_update"], reverse=True)


def get_changelog(package_name: str, from_version: str, to_version: str) -> str:
    """Fetch changelog from npm registry or GitHub."""
    try:
        # Try npm registry for repo URL
        registry_url = f"https://registry.npmjs.org/{package_name}"
        resp = requests.get(registry_url, timeout=5)
        data = resp.json()
        
        repo = data.get("repository", {})
        if isinstance(repo, dict):
            repo_url = repo.get("url", "")
        else:
            repo_url = str(repo)
        
        # Clean up GitHub URL
        if "github.com" in repo_url:
            repo_path = repo_url.split("github.com/")[-1].rstrip(".git")
            
            # Fetch releases from GitHub API
            releases_url = f"https://api.github.com/repos/{repo_path}/releases"
            releases_resp = requests.get(releases_url, timeout=5)
            releases = releases_resp.json()
            
            if isinstance(releases, list):
                relevant = [
                    r["body"] for r in releases[:10]
                    if isinstance(r, dict) and r.get("body")
                ]
                return "

---

".join(relevant[:5])
    except Exception:
        pass
    
    return f"Could not fetch changelog for {package_name}"


def analyze_upgrade_risk(packages: list[dict]) -> str:
    """Use Claude to analyze breaking changes and generate upgrade plan."""
    
    # Build context with changelog info for major updates
    context_parts = []
    for pkg in packages[:15]:  # Limit to 15 most important
        if pkg["is_major_update"]:
            changelog = get_changelog(pkg["name"], pkg["current"], pkg["latest"])
            context_parts.append(
                f"## {pkg['name']}: {pkg['current']} -> {pkg['latest']}
{changelog[:2000]}"
            )
        else:
            context_parts.append(
                f"## {pkg['name']}: {pkg['current']} -> {pkg['latest']} (minor/patch)"
            )
    
    context = "

".join(context_parts)
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=2000,
        system="""You are a senior engineer reviewing npm dependency upgrades.
For each package, assess:
1. Risk level (HIGH/MEDIUM/LOW) based on breaking changes
2. What specifically breaks
3. Migration steps needed
4. Recommended upgrade order (start with low risk)

Format as a prioritized upgrade plan with clear sections.""",
        messages=[{
            "role": "user",
            "content": f"Analyze these package updates and create an upgrade plan:

{context}"
        }]
    )
    
    return response.content[0].text


if __name__ == "__main__":
    print("Checking outdated packages...")
    packages = get_outdated_packages()
    
    if not packages:
        print("All packages up to date!")
        exit(0)
    
    major_count = sum(1 for p in packages if p["is_major_update"])
    print(f"Found {len(packages)} outdated packages ({major_count} major updates)
")
    
    plan = analyze_upgrade_risk(packages)
    print(plan)

Example output

text
## Upgrade Plan

### HIGH RISK — Do Last, Requires Migration

**next: 14.x -> 15.x**
Breaking changes:
- `cookies()`, `headers()` are now async — every Server Component using these needs `await`
- `params` in page.tsx is now a Promise — must be awaited
Migration: Run `npx @next/codemod@canary upgrade latest` first, then manually fix remaining issues
Estimated effort: 2-4 hours

### MEDIUM RISK — Test Thoroughly

**zod: 3.22 -> 3.23**
Breaking: `.email()` validation is now stricter (rejects some previously valid emails)
Migration: Review any email validation and update test fixtures
Estimated effort: 30 minutes

### LOW RISK — Safe to Upgrade

These packages have no breaking changes:
- axios: 1.6 -> 1.7 (security fix)
- date-fns: 3.3 -> 3.6 (new functions added)
- lodash: 4.17.20 -> 4.17.21 (security patch)

Recommended order: lodash → axios → date-fns → zod → next

Running in CI on a schedule

yaml
# .github/workflows/dep-audit.yml
name: Weekly Dependency Audit
on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9am
  workflow_dispatch:

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - name: Run audit
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: python3 scripts/dep-audit.py > dep-audit.md
      - name: Create issue
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const body = fs.readFileSync('dep-audit.md', 'utf8');
            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Weekly Dependency Audit',
              body,
              labels: ['dependencies']
            });

The weekly GitHub issue gives the team a clear, actionable upgrade plan every Monday. Dependencies stop accumulating for quarters and the upgrades become manageable incremental work.

Share this
← All Posts6 min read