Make Your Coding Agent Wait 7 Days for New Packages: npm, pnpm, Yarn, Bun and uv
Book: AI That Acts The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other
- Book: AI That Acts
- The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
The malicious chalk release was live for under 12 hours. The Nx one for about 4 hours, rspack for about an hour. William Woodruff lined up ten well-known supply-chain attacks and found that 8 of them had a window of opportunity shorter than a week (his post).
Put your coding agent inside that window. It decides it needs a date library, runs npm install a few seconds later, and the registry hands it the newest version. Nobody has looked at that version yet, including the scanners and the maintainer whose account was phished an hour ago.
The fix is a release-age gate, also called a cooldown: refuse any version younger than N days. npm, pnpm, Yarn, Bun and uv all have one now. They spell it five different ways, in four different units, and one of them does nothing at all on an npm that is a few months old.
One setting, five spellings
Here is the 7-day version of the gate in each manager, with the file it lives in and the release that added it.
| Manager | File | Key | Unit | 7 days | Since |
|---|---|---|---|---|---|
| npm | .npmrc |
min-release-age |
days | 7 |
11.10.0 |
| pnpm | pnpm-workspace.yaml |
minimumReleaseAge |
minutes | 10080 |
10.16 |
| Yarn | .yarnrc.yml |
npmMinimalAgeGate |
duration | "7d" |
4.10.0 |
| Bun | bunfig.toml |
minimumReleaseAge |
seconds | 604800 |
1.3 |
| uv | pyproject.toml |
exclude-newer |
duration | "7 days" |
0.9.17 |
Sources for each row: npm 11.10.0 and the npm config docs, pnpm 10.16, Yarn's PR #6901, Bun 1.3, uv 0.9.17.
Get the unit wrong and the gate is either useless or absurd. minimumReleaseAge: 7 in pnpm is seven minutes. minimumReleaseAge = 7 in Bun is seven seconds. min-release-age=10080 in npm is about 27 years.

A cooldown lets a new version sit for a week before anything on your machine is allowed to install it.
npm: the key that older npm accepts and ignores
In npm, the gate is one line in the project's .npmrc. The current npm 11 config docs also list an exclude key that takes names or globs, for your own packages:
# .npmrc (commit this one)
min-release-age=7
min-release-age-exclude[]=@myorg/*
The docs say npm "will build the npm tree such that only versions that were available more than the given number of days ago will be installed", and that if no version fits, "the command will error." If npm audit fix is blocked because the patched version is too new, npm keeps the vulnerable version, warns, and exits non-zero. That is your cue to add the package to the exclude list on purpose.
Only exempt a scope you own on the public registry. The npm config docs say you should own your private scope there "so that nobody else can publish a package with the same name." If you don't, someone else can publish @myorg/anything and walk past the gate. The same goes for the exclude lists in pnpm, Yarn and Bun below.
The trap is the version. min-release-age arrived in npm 11.10.0, released February 11, 2026. An older npm reads your .npmrc, does not know the key, and installs with no gate. From npm 11.2.0 on you at least get an unknown-config warning (npm docs). Before 11.2.0 you get nothing. The file in your repo says 7 days, and nothing on that machine enforces it.
So check the version before anything installs. A small script does it on macOS, Linux and Windows:
// scripts/check-npm.mjs
import { execSync } from "node:child_process";
const v = execSync("npm -v").toString().trim();
const [major, minor] = v.split(".").map(Number);
if (major < 11 || (major === 11 && minor < 10)) {
console.error(`npm ${v} ignores min-release-age.`);
console.error("Upgrade npm to 11.10 or later.");
process.exit(1);
}
Keep registry tokens in your user-level ~/.npmrc. The project .npmrc is going into git, and from there into every worktree.
pnpm, Yarn and Bun
pnpm counts in minutes. Since pnpm 11 it reads pnpm settings from pnpm-workspace.yaml and keeps .npmrc for registry and auth only (pnpm 11.0):
# pnpm-workspace.yaml
minimumReleaseAge: 10080 # minutes, 7 days
minimumReleaseAgeExclude:
- '@myorg/*'
pnpm 11 already defaults to 1440 minutes, one day. The detail that matters is in minimumReleaseAgeStrict: it is true only when you set minimumReleaseAge yourself. With the default, pnpm falls back to a version that breaks the age rule when nothing else fits. Set the value explicitly and pnpm fails resolution instead. The exclude list takes names, globs and exact versions like [email protected] || 5.102.1.
Yarn calls it an age gate and calls the exclusions pre-approved packages, which take descriptors or name globs:
# .yarnrc.yml
npmMinimalAgeGate: "7d"
npmPreapprovedPackages:
- "@myorg/*"
Yarn keeps the value in minutes: in Classmethod's test, yarn config get npmMinimalAgeGate reported "21d" as 30240. For a range, Yarn installed the newest version that passed, and the gate held for transitive dependencies too. An exact pin that is too new fails with "No candidates found" in the PR's tests.
Bun counts in seconds and sits under [install]:
# bunfig.toml
[install]
minimumReleaseAge = 604800 # seconds, 7 days
minimumReleaseAgeExcludes = ["@myorg/shared"]
Bun's docs say it "filters out package versions published more recently than this threshold." Its exclude list takes package names, or name@version to exempt one exact release. The docs show no scope wildcard, so list your own packages by name.
uv: a duration, frozen into the lockfile
uv uses exclude-newer, which takes an exact timestamp. Since 0.9.17 it also takes relative durations, and that is what turns it into a cooldown:
# pyproject.toml
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { my-internal-lib = false }
It accepts friendly durations ("7 days", "1 week") and ISO 8601 ones ("P7D"), but no months or years. false exempts one package. Three things behave differently from the JavaScript managers, per uv's resolution docs:
- uv turns "7 days" into a timestamp at resolution time and writes it into
uv.lock. It stays put until a new resolution, for example with--upgrade, so the lockfile doesn't churn every day. - The cutoff is checked against each file's upload time, not the version's release date. A wheel uploaded to an old version yesterday is too new.
- It covers registry packages only. A Git dependency is not gated.
Commit it once, and every agent worktree has it
A git worktree is a checkout of tracked files. Nothing ignored or untracked in your main checkout comes along. A setting in your user-level ~/.npmrc reaches every worktree on your machine, but not a teammate's laptop, CI or a container. That is why the gate belongs in a committed file: .npmrc, pnpm-workspace.yaml, .yarnrc.yml, bunfig.toml or pyproject.toml. Any agent that runs an install in any checkout of the repo reads it.
This is where Hermes IDE fits. I build it, so weigh this part with that in mind. In Hermes, ⌘N (Ctrl+Shift+N on Windows and Linux) starts each task on its own hermes/<name> branch, in its own worktree, with the task as the agent's first prompt. Hand a task to Claude Code there and the agent's npm install runs in a fresh checkout that already has your .npmrc.

The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, and the command Hermes will run.
The committed file covers the setting. The npm version check needs to run somewhere too. Hermes reads .hermes/worktree.toml from your repo and runs its setup commands in each new task's worktree before the agent starts. The first command that fails stops the rest (recipe docs):
# .hermes/worktree.toml
setup = ["node scripts/check-npm.mjs", "npm ci"]
done_when = ["npm test"]
The first time Hermes sees the recipe, and again whenever it changes, it shows you the commands and waits until you choose Run setup. On a machine with npm 11.9, the check fails, npm ci never runs, and the failure stays in the task's setup card and lands in your inbox, so you see it before you hand that worktree to an agent. Choosing Skip starts the session without running setup, and that skips the check too. Commit the recipe next to the .npmrc so both travel together.

The Review Desk's Worktrees tab: one worktree per task, each on its own hermes/ branch, with its task, age and disk size.
Watch it refuse
Test the gate on your own machine before you rely on it. The TypeScript nightly makes a good test, because a new build from main is published to npm by midnight PST every day (TypeScript docs). Its newest version is normally less than a day old.
npm -v
v=$(npm view typescript@next version --min-release-age=0)
npm view typescript time --json --min-release-age=0 \
| grep "\"$v\""
npm install -D "typescript@$v"
The two npm view calls pass --min-release-age=0 so the gate can't hide the newest version or its publish time from you. The install keeps your .npmrc setting. With the gate working, npm stops with npm error code ETARGET and a notarget No matching version found for typescript@... line that names the cutoff date. That is the error Classmethod got pinning a too-new eslint. If the install succeeds instead, npm -v on the first line tells you why.
The same pin works as a test for the other managers: pnpm add -D "typescript@$v" should fail resolution, since you set minimumReleaseAge yourself. Run it before you trust it.
Know what seven days does not cover. In Woodruff's list, xz-utils had a window of about five weeks, and only a longer cooldown gets near it. A cooldown also delays the good releases, including security fixes, which is what the exclude lists and npm's audit fix warning are for. It doesn't read the code either. It only makes sure someone else got a week to.
If this was useful
A coding agent is a model with a shell tool, and npm install is one of the calls it makes on its own. AI That Acts is the book in my AI in TypeScript series about tool calling: how a model picks a function, what you let a tool do, and how to build a first working agent in TypeScript around those decisions.
The whole series, from your first LLM call to agents in production, is at xgabriel.com/ai-in-typescript.
If you run several agents on one repo, Hermes IDE is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux (GitHub).
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.
