Stop AI Coding Agents Adding Dependencies You Did Not Pick

The fastest way to stop an AI coding agent adding dependencies is to make the lockfile a reviewed artifact and fail the build when it changes without approval. A single check that compares the lockfile against the last committed version, plus an allowlist of packages the agent may reach for,...

Steve Jefferson
Steve Jefferson
Developer Advocate
15 August 20261 min read

The fastest way to stop an AI coding agent adding dependencies is to make the lockfile a reviewed artifact and fail the build when it changes without approval. A single check that compares the lockfile against the last committed version, plus an allowlist of packages the agent may reach for, turns "the agent quietly added seven packages" into a red build that names all seven. Asking nicely in an instructions file does not survive a long session.

This is worth caring about even if you like the packages. Every dependency an agent adds is one you did not evaluate, did not check for maintenance status, and will still be carrying in eighteen months.

Why agents reach for packages

An agent solving a problem picks the solution with the highest probability of working, and in its training data the highest-probability solution to almost anything is "install a library". That is a reasonable prior. Most of the code on the internet does exactly that.

It goes wrong in three specific ways.

Reinventing what you already have. The agent adds a date library to a project that already depends on one, because it did not read your existing dependency list before deciding.

Solving a two-line problem with a package. A left-pad-shaped function arrives as a new transitive tree of eleven packages.

Picking the popular option rather than the maintained one. Popularity in training data reflects the state of the ecosystem some time in the past. The package with the most stars in 2024 may have been unmaintained since.

And separately, the failure mode where the package does not exist at all, which is its own problem covered in when AI hallucinates a package that does not exist.

The gate: fail on unexpected lockfile changes

The lockfile is the single best signal because it changes whenever the dependency graph changes, including transitively, and it changes in a way you can diff.

Put this in CI and in your local check command:

bash
#!/usr/bin/env bash
# scripts/check-deps.sh
set -euo pipefail

# Reinstall from the lockfile only. Fails if package.json and
# the lockfile disagree, which is exactly what an agent edit causes.
npm ci --ignore-scripts >/dev/null

# Fail if the working tree changed the lockfile relative to the base branch.
if ! git diff --quiet "origin/${BASE_BRANCH:-main}" -- package-lock.json; then
  echo "Lockfile changed. New or updated dependencies in this change:"
  git diff "origin/${BASE_BRANCH:-main}" -- package-lock.json \
    | grep -E '^\+ +"(node_modules|)' | head -50
  echo
  echo "Dependency changes require explicit human approval."
  exit 1
fi

The equivalent exists everywhere. poetry lock --check, cargo update --locked, go mod verify plus a diff on go.sum, bundle install --frozen. The mechanism is identical: the lockfile is authoritative, and a change to it is an event. Note that npm ci is documented to fail outright when the lockfile and the manifest disagree, which is precisely the state an agent edit leaves you in.

Note --ignore-scripts. Install scripts run arbitrary code, and an agent-added package running an install script is not a hypothetical you want to test in CI.

The allowlist: say yes to a short list, no to everything else

A gate that blocks every dependency change becomes something people route around. Pair it with a list of packages the agent may add without asking, which for most repos is short and mostly consists of things already present.

json
{
  "allowedNewDependencies": [
    "@types/*",
    "zod",
    "date-fns"
  ],
  "rationale": "Types are free. zod and date-fns are already load-bearing here; anything else needs a human."
}

Then in your instructions file, one paragraph the agent reads on every task:

markdown
## Dependencies

Do not add, upgrade, or remove any package unless it matches
scripts/allowed-deps.json. If a task seems to need a new
dependency, stop and explain what you would add and why,
including what you checked in the existing dependencies first.
Prefer standard library. Prefer 30 lines of our code over a
new transitive tree.

The "stop and explain" instruction is doing real work. It converts a silent install into a proposal you can accept in ten seconds or reject in two. Where the rest of that file goes is covered in our guide to the repo instructions file agents read first.

Make the existing dependencies visible

A surprising share of duplicate-dependency incidents happen because the agent never looked. Fix that by putting the inventory where it reads.

Generate a short manifest and commit it:

bash
npm ls --depth=0 --json \
  | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{
      const d=JSON.parse(s).dependencies||{};
      console.log('# Direct dependencies\n');
      for (const [k,v] of Object.entries(d)) console.log(`- ${k} ${v.version}`);
    })" > DEPENDENCIES.md

Then reference it: "Before proposing any dependency, read DEPENDENCIES.md and state which existing package you considered and why it does not fit." That single sentence catches most of the duplicate cases, because the answer is usually that an existing package does fit.

Review what gets through

Some dependency changes are correct and will be approved. Approve them with a checklist rather than a shrug, because this is the moment where supply chain risk enters your project.

Check

Why

Fast way to do it

Last release date

Unmaintained packages accumulate unpatched issues

Registry page, or npm view <pkg> time.modified

Transitive count

A small package with 40 dependencies is not a small package

npm ls <pkg> after install, in a scratch branch

Install scripts

Arbitrary code execution at install time

npm view <pkg> scripts

Name plausibility

Typosquats target exactly this workflow

Read the name character by character against the docs

Licence

Some licences are incompatible with your distribution

npm view <pkg> license

Does it exist upstream

Guards against a confidently invented package

Open the repository, look at commits

That last row is not paranoia. The failure mode where a model invents a plausible package name is well documented, and an attacker who registers the invented name gets code execution in every project whose agent tries to install it.

Handle upgrades separately from additions

Agents also silently bump versions, usually while fixing something unrelated. Treat that as a different event with different rules.

Additions need a human decision about scope. Upgrades need a human decision about risk. A patch bump on a dev dependency is not the same as a major bump on your ORM, and a single "dependencies changed" alert that treats them identically will be ignored within a fortnight.

The cheapest split: allow patch upgrades on dev dependencies automatically, block everything else. Most agent-initiated version churn falls into the first bucket, so you cut the alert volume hard without losing the signal that matters.

The failure this actually prevents

Six months of unsupervised agent work on a mid-sized service, and the dependency list has doubled. Nobody chose those packages. Nobody can say which are load-bearing. The build is slower, the container is larger, and the next security advisory lands on a package no human on the team has heard of.

None of that is dramatic on any given day, which is precisely why it needs a mechanical gate rather than good intentions. The lockfile check takes twenty minutes to set up and it is the whole defence. It belongs alongside the other guardrails in our overview of what AI coding tools can and cannot be trusted with.

While you are adding gates, the adjacent one worth having is a secret scanner, since an agent leaking API keys is the other unsupervised-change risk that a pre-commit hook catches cheaply. Both belong in the same review pass, and if you run agents unattended, in CI is where these checks earn their keep.

Frequently asked questions

Is blocking dependencies going to make the agent worse at its job? Slightly slower on tasks that genuinely need a library, and better on everything else. The instruction to propose rather than install keeps the capability while moving the decision.

What about monorepos with dozens of packages? Run the lockfile check per workspace and keep the allowlist per workspace too. A dependency that belongs in the API service rarely belongs in the shared types package.

Does a private registry solve this? It solves typosquatting and gives you an audit trail, which is real value. It does not stop the agent adding four maintained-but-unnecessary packages, so you still want the gate.

Should the agent be allowed to remove dependencies? Removals are lower risk than additions but should still be visible, because an agent removing a package that is only used at runtime through dynamic import will break production and pass CI.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.