AI coding

How to Read a Dependency Change an Agent Made

Antonio Evans10 min read

A reviewer's desk seen from above: a terminal window showing a git diff of a lockfile with a few added lines highlighted, next to a printed dependency tree diagram with one new branch circled.
Key points

Key points

  1. 1Read the lockfile diff by counting added package entries, not added lines, because one direct dependency bump can pull in a dozen transitive records.
  2. 2Resolve the tree before reading code, so you know who pulled the package in and whether two versions of it now coexist.
  3. 3Check the license of the added package and every new child it dragged in, not just the one name the agent mentioned.
  4. 4Find and read every install-time script, since those run with your build credentials before any test does.

Start with three commands, not the diff prose

An agent's description of a dependency change is generated text, so it can be plausible and still wrong about what it did. The reviewer's first job is to get a small set of facts that the description cannot distort. Three commands give you those facts in under a minute.

The first is a count of changed lockfile entries. A single bump to a popular package can resolve to new versions of several transitive packages at once, and the diff shows each one as a separate record. Count the added records and you know immediately whether you are reviewing one change or thirty.

The second is a resolved tree query. It answers "who depends on this" and "how many copies are installed." Both matter: a package you never chose, arriving two levels deep, is a supply chain decision you did not make consciously.

The third is a read of the package manifest for install-time hooks. Package managers run certain scripts automatically during install, which means they execute before your tests, your linter or your review ever touch the code.

# 1. How many lockfile records does this change add or remove?
git diff --stat origin/main...HEAD -- 'package-lock.json' 'pnpm-lock.yaml' 'yarn.lock' 'poetry.lock' 'uv.lock'

# 2. Who pulled it in, and are there duplicates?
npm ls <package-name>
# or, for a Python project
pip show <package-name>

# 3. Does it run anything at install time?
node -e "const m=require('./node_modules/<package-name>/package.json');console.log(m.scripts)"

The file list comes before all of this. Pull the changed paths with a name-only diff, because agents routinely edit files the request never named, and the Open Worldwide Application Security Project (OWASP) secure coding with AI cheat sheet calls unnoticed out-of-scope edits the most common review failure mode in agentic coding, as SpecStory's review checklist summarizes. If the lockfile changed but no manifest did, or the reverse, stop and ask why before reading further.

Counting lockfile entries instead of lines

Lockfiles are machine-written and heavily reformatted, so line counts mislead in both directions. A reordering can produce hundreds of changed lines with zero semantic effect. A genuine bump can produce twenty changed lines that add a package with an install script.

Work in records, not lines. For package-lock.json, filter the diff for the lines that name a package and version:

git diff origin/main...HEAD -- package-lock.json \
  | grep -E '^\+.*"resolved"' \
  | sort -u | wc -l

The number that comes back is your review budget. A worked example: suppose the diff shows 212 added lines but only 9 distinct new resolved URLs. You have nine packages to check, not two hundred lines. Set a threshold in your team's review guide, say more than five new resolved packages for a change that claims to add one library, and treat crossing it as a signal to ask the agent to split the change or explain the fan-out.

Four things in a lockfile diff deserve a look every time:

Lockfile fieldWhat a change meansReviewer action
resolved URLThe registry or host the tarball comes fromLoad the URL in a browser; confirm the canonical registry host, not a mirror or a personal fork
integrity hashThe exact bytes to be installedHash change on an unchanged version is a red flag; investigate before install
version stringWhich release resolvesConfirm it is the release you expected, and check whether a major version jumped
dependencies blockThe package's own declared requirementsCompare with the registry listing; a mismatch means the tarball and the registry record disagree

The integrity hash is the strongest single control you have. It pins the exact artifact, and if a version you already had suddenly shows a different hash, the package was republished or tampered with. GitHub's own guidance on reviewing AI-generated code tells reviewers to be vigilant about new packages and to verify that suggested dependencies exist and are maintained, which starts with reading where they resolve from, per GitHub's review guide.

Registry strings are also where slopsquatting shows up. An attacker registers a name that a model might invent, and if the lockfile entry points at a package whose README, owner or first publish date does not match the library the agent described, you have caught it before npm install runs. Metacto's review standards treat dependency provenance as a pre-screening standard for exactly this reason: confirm the package exists on the canonical registry, matches the intended library, is needed at all, and is pinned through the lockfile.

Reading the resolved tree to find who pulled what

A lockfile tells you what is installed. The resolved tree tells you why. npm ls <package> prints the path from your project to the package, so you can see whether it came from the agent's direct addition or from something else entirely, and the command exits non-zero when the installed tree does not match what the manifest says should be there, as this walkthrough of npm ls explains.

Two outputs matter more than the rest. First, duplicates: if npm ls prints two entries for the same package at different versions, your bundle now carries both, and one of them may be the version with the known problem. Second, paths you did not expect: a package arriving under a build tool you never touched is a change to your toolchain, not to your feature.

For Python, pip show <package> gives you the version, the location, the license line and, critically, the Required-by field, which lists the installed packages that depend on it. pipdeptree --reverse --packages <name> draws the same picture as a tree.

Depth deserves a sanity check of its own. A survey of npm dependency trees compared npm install --dry-run counts across libraries and found enormous variance: some state libraries add a single package, a web framework added 57, and a full application framework added roughly 524. The number is not a verdict on quality, but it is a verdict on how much you are about to audit, and it belongs in the pull request description when it is large.

The decision rule for a bloated tree

Here is a rule you can apply without judgment calls, which is what makes it usable on a busy review queue. Suppose the agent's direct addition is one package, and the resolved tree shows it adds more than ten transitive packages for a single utility function. Ask the agent one question: what does the new package do that the standard library or an existing dependency does not?

If the answer is a one-line operation the platform now handles natively, reject the dependency and ask for the inline version. If the answer is a real capability such as a parser, a database driver or an image pipeline, accept it and record the tree size so the next reviewer inherits your analysis. The economics are simple: the time to audit ten packages once, at the moment someone chose them, is far smaller than the time to audit them after an incident when nobody remembers the choice.

The same rule handles the second copy of a package. If npm ls shows two versions of the same library, either unify them with an override in the manifest or accept the duplication with a written reason. A guide to npm overrides documents the syntax, including the dot key that targets the parent package itself and its children separately, and notes that overrides are meant for exactly two jobs: fixing a vulnerable transitive dependency and aligning versions across packages.

Reading the license on the package and its new children

License review is the step most often skipped, and it is the one that produces the least recoverable problems, because a license obligation does not go away when you delete the code later. GitHub's review guidance names the concrete failure: introducing a dependency that is incompatible with your project's license, with AGPL in an MIT project and packages with no declared license given as the examples, per GitHub's review guide.

Read the license of the direct addition first, then walk the new children. The children matter because copyleft obligations propagate through the dependency graph, and the agent almost never mentions them. A practical pass:

# Node: every installed package's declared license, sorted by frequency
npx license-checker --summary

# Python: license line for the package you are reviewing
pip show <package-name> | grep -i license

Then apply a three-tier rule and write it into your review template so nobody has to renegotiate it per pull request:

TierLicensesDefault decision
PermissiveMIT, Apache-2.0, BSD, ISCAccept; note the license in the pull request
ConditionalLGPL, MPL, EPLAccept only if you are not modifying the library and the obligation is understood by someone accountable
RestrictedAGPL, SSPL, no declared license, custom termsBlock until a license owner signs off in writing on the pull request

Two details catch people. A package with no declared license is not public domain; default copyright means you have no grant to redistribute it. And a license field that does not match the LICENSE file in the repository is a question for the maintainer, not something to resolve by assuming the friendlier of the two applies.

Install scripts run before your tests do

This is the part of a dependency change that executes code on your machine and your CI runners without any of your own code being called. preinstall, install and postinstall scripts in package.json run during npm install, and their equivalents in Python (setup.py with a custom install command, build backend hooks in pyproject.toml) run when a package is built from source.

The word "before" is the whole problem. A malicious or merely careless install script runs with whatever credentials the install step has: your registry token, your CI secrets if they are in scope, your network position inside the build environment. A collection of npm security best practices puts supply chain hardening, safe-by-default package manager options and deterministic dependency resolution in scope for this reason, and the package manager gives you a blunt control: skip the scripts.

# Read what the package would run, without running it
node -e "console.log(require('./node_modules/<pkg>/package.json').scripts || {})"

# Or inspect the tarball without installing it
npm pack <package-name> --dry-run

# Install with lifecycle scripts disabled
npm install --ignore-scripts

Disabling lifecycle scripts for a single CI job is a cheap way to find out whether your build actually needs them. Most projects do not: the packages that legitimately need a postinstall are the ones with native addons that compile at install time, and those will fail loudly rather than silently. If the build breaks with --ignore-scripts, you now have a short, evidence-based list of packages that need scripts, and everything else can stay disabled.

For a package whose script you decide to keep, read it line by line. Look for network calls (curl, wget, fetch against an unfamiliar host), reads of environment variables that look like secrets, writes outside the package directory, and shells invoked with a string built from anything downloaded. A script that downloads a binary and executes it is doing nothing wrong in principle and everything worth a second reader in practice.

Vendored drops and the diff that hides everything

A vendored dependency is source copied into your repository rather than fetched by a package manager. It appears in a review as a large block of new files, often thousands of lines, and the reviewer's instinct is to skim and approve. That instinct is the vulnerability.

The narrow question for a vendored drop is not "is this good code" but "is this the code the agent said it is." Three checks answer it. First, compare a hash: if the agent claims the source came from a tagged release, compute a checksum of the vendored tree and compare it with the upstream tarball for that tag. Second, read the license file in the vendored directory and confirm it matches what your policy expects, because vendoring does not change the license, it only changes who has to comply with it. Third, list what was removed: vendored code is often trimmed, and a trimmed copy is a fork you now maintain, which should be a stated decision and not a side effect.

Then look for capability the vendor does not need. A vendored library that opens sockets, spawns processes or writes to the filesystem, when your use of it is a string formatting function, is a larger attack surface than the feature justifies. Ask for the unused modules to be deleted or for the dependency to be fetched normally with an integrity hash. The lockfile is a control you lose the moment code is copied in by hand.

Where human review still has to happen

Reviewing dependency changes is repetitive, which makes it a good candidate for automation, and a poor candidate for full delegation. Two useful anchors keep the split sane. Google's review standard says reviewers should approve a change once it definitely improves overall code health, even if it is not perfect, which argues for automating the counting, hashing and license lookups and reserving human attention for the judgment calls, per Google's review standard. And the same guide's list of what to look for names design and complexity as the things reviewers weigh most, which is precisely what a machine cannot assess about a vendored drop, per what to look for in a code review.

The judgment calls that stay human are predictable. Is this dependency needed at all, or does the codebase already have the capability? Is the maintainer real and reachable? Does the license obligation fit how we distribute the product? And for install scripts, is the network call this script makes consistent with what this package claims to do? Those four questions are cheap to answer and expensive to skip, and a checklist in the pull request body makes them visible to the next reviewer. Thoughtbot's guidance on reviewing AI-generated pull requests makes the same point about existing code: agents frequently reimplement something the codebase already has, so the first question about a new dependency is whether an existing one already covers the need.

For teams that want the mechanical pass done before a human opens the file, the workflow fits a coding agent that already has the commands above in its skill set. Quanta Skills is the library those agents install from, and a dependency-review skill can run the lockfile count, the tree query and the license summary and hand back a table like the ones above for a person to sign off.

FAQ

FAQ

What is the first thing to check when an agent adds a dependency?

Count the new lockfile records and check where each one resolves from. That single pass tells you the size of the review, whether any package came from an unexpected host, and whether the agent's description matches what the lockfile actually added.

How do I tell whether a version bump is safe?

List the resolved tree for the bumped package and compare the versions that now coexist. If the bump changed a major version, read the changelog for the breaking changes and check that no other dependency still requires the old major, because two copies of a library can both be live in the same process.

Should I trust a package's install script?

Read it, then decide. Install scripts run automatically with the permissions of the installing process, so treat each one as code you are about to execute, and disable all lifecycle scripts by default in CI so that only the packages you have read need an exception.

What does a changed integrity hash mean?

The artifact at that version is not the bytes you previously installed, either because the package was republished or because something in the path was tampered with. Diff the contents against the repository for that tag before you install, and if the difference is unexplained, pin a version you can verify instead.

How much of a vendored dependency should I actually read?

Read the license, the entry points and anything that touches the network, the filesystem or processes. For the rest, verify by hash that the vendored tree matches the upstream release you intended, and require a stated reason for anything that was trimmed, because a trimmed copy is a fork you maintain.

Sources

Sources

  1. Code review checklist for AI-generated code - SpecStory
  2. Review AI-generated code - GitHub Docs
  3. AI-Generated Code Review Checklist: 2026 Standards for Teams - Metacto
  4. Using npm's ls command for Fun and Insight
  5. npm Dependency Trees: Most Nested Packages 2026
  6. A Guide to NPM Overrides: Take Control of Your ...
  7. Collection of npm package manager Security Best Practices
  8. The Standard of Code Review | eng-practices - Google
  9. What to look for in a code review | eng-practices - Google
  10. How to review AI generated PRs - Thoughtbot
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles