whybother

Field Guide · 03 of 10

Why AI agents add dependencies you never needed

Agents reach for a package where a few lines would do, answer a vulnerability report with a forced upgrade, and now and then import a name that does not exist. Every one of those leaves a trace in the manifest and the lockfile. The check is to read what actually landed, not what the summary says landed.

What is actually happening

Most tutorials start with an install. They show the package, then the usage, and rarely ask whether the package was needed. An agent trained on that material inherits the order of operations: install first, think later. A date format, a padding helper, a deep clone: each is a five-line function, and each tends to arrive as a dependency with its own dependencies.

The fix for the resulting audit report is the same habit. npm audit fix --force is allowed to install breaking, semver-major versions to clear a finding. It can change what your code runs against, and it can leave more findings than it started with. In the skit the count goes from 312 to 529, which is a joke with a realistic shape: a forced upgrade can pull in new packages, and new packages bring findings of their own.

The third problem is the strangest. A model suggesting a package sometimes produces a name that sounds right and does not exist. Anyone can register that name afterwards. If an attacker watches for the names that get suggested and publishes a package under one, the next person to run the install gets the attacker's code. The practice is called slopsquatting. Nothing about it needs the agent to be malicious, only fluent.

A dependency is a decision someone else will keep making for you, on every release, forever. An agent does not carry that cost, so it never prices it.

How it shows up in the diff

 "dependencies": {
+   "padstring-ultra": "^2.0.1",
+   "padstring-but-async-ai": "*",
   "react": "^18.3.0"
 }

Guardrail

The rule is to make the agent ask, and to make the answer cheap to check.

Paste into AGENTS.md

Do not add a dependency without asking. Name it, say why the standard library is not enough, and link its registry page.

Check it mechanically

# which dependency lines did this branch add or change?
git diff -U0 origin/main...HEAD -- package.json | grep -E '^\+\s+"[^"]+":\s*"'
# for each new name:
npm view <name> time.created repository.url

The first command prints every added line of the form "name": "version", so version bumps appear alongside new packages, and so do added scripts and other string fields; read it as a list to look at, not a clean inventory. The second asks the registry when the package was first published and where its source lives. A package created last week with no repository is a question, not a dependency. A name the registry does not know at all returns a 404, which almost certainly means the name was invented. It can also mean a private or scoped package hosted on another registry, so check before you assume.

Then run npm audit and read it. Many findings are in development tooling that never ships, or in a code path you do not use. Some are real, and the fix is a specific upgrade you can review. Do not use --force to make the number go down; a lower number is not the goal, a known state is.

Further reading

Choose Boring Technology: every dependency spends some of a limited budget, so spend it deliberately.