Hermes Now Gates Package-Manager Uninstalls Behind Approval

A global npm package is quietly slowing your builds, you trace it down, and you type npm uninstall -g some-package without a second thought. Two days later CI is missing it, and every project on that dev machine is affected. Installing something is easy to notice; removing something is easy to forget, and the damage is usually shared. Yet when Hermes Agent ran that command for you, nothing stopped to ask — the dangerous-command approval gate only knew about shell primitives like rm -rf. PR #109170 closes that gap.
Installs rarely hurt. Removals do.
First, the mechanism. Dangerous-command approval is a built-in set of pattern rules in Hermes: when the agent is about to run a command whose consequences can’t be undone, it shows an approval prompt and waits for you instead of executing. The design intent is plain — don’t let an irreversible operation slip past unnoticed.
Installing and removing are not symmetric under that lens. Installing is additive: whatever shows up, you notice it soon enough, and if it causes trouble you delete it. Removing is subtractive, and usually shared subtraction — uninstall one global package and every project on the machine that depended on it is affected, with nothing telling you that something just disappeared.
That asymmetry is exactly why this change gates removals and only removals. npm update -g, pip install, brew upgrade, and yarn install are still not flagged and still don’t prompt. Gate the install side too and the approval bar becomes noise you click through a dozen times a day — worse than not gating at all.
The pattern table only knew shell primitives
The problem lived in DANGEROUS_PATTERNS, the pattern table in tools/approval_detection.py. What it caught were shell primitives: rm -rf, git reset --hard, and friends — blunt, widely known, obvious on sight. There was no rule at all for package-manager delete verbs.
So when the incident behind issue #10199 actually happened, the command ran to completion with no prompt whatsoever. Not a wrong verdict — the command simply was not covered by any rule.
The five removal spellings that now get stopped
PR #109170 takes the direct route: it adds one "package manager uninstall" rule per package manager to DANGEROUS_PATTERNS. The whole change is +34 / −0 across two files, tools/approval_detection.py and tests/tools/test_approval.py.
From here on, these go through dangerous-command approval:
- npm —
npm uninstall -g some-package. Delete verbs:uninstall/unlink/remove/rm/r/un - pnpm —
pnpm remove. Delete verbs:uninstall/remove/rm/un - yarn —
yarn remove. The rule allows one optionalglobalbefore it, thenuninstall/remove - pip / pip3 —
pip uninstall. The delete verb isuninstall - brew —
brew uninstall. Delete verbs:uninstall/remove/rm
Note what the rules match: the manager’s delete verb, not a specific package name. Whatever the verb is aimed at — and however many targets follow — the command gets flagged.
Two places they avoided friendly fire
The classic way to break a rule table is to make a rule too broad and start stopping legitimate commands. Two details here are worth calling out.
First, the rules are anchored to command position with _CMDPOS. Like the other command-name rules in that file, the pattern only matches where the command actually starts. Text inside quotes therefore isn’t misread — git commit -m "document npm uninstall usage" is just a commit message and won’t prompt.
Second, each global option is allowed one non-dash argument. So npm --prefix DIR uninstall x, pip --proxy URL uninstall x, and yarn --cwd DIR remove x are all still caught. An option followed by an argument is an everyday shape; miss it and the rule is decorative.
The tests came along too. tests/tools/test_approval.py adds 6 parameterized cases that collapse six delete spellings onto a single key. Those cases were red against the old code; after the fix, 129 cases pass, and the tests/tools/ suite as a whole is green at 9141.
What this means for you
If someone is watching your Hermes session, this is simple: the agent stops and asks before it uninstalls a package, and you confirm or decline. One extra click buys you “the thing that got deleted is the thing you meant to delete.”
The case that deserves real thought is unattended work — cron jobs, agents running in the background. Nobody is there to answer a prompt, so a removal that should have been stopped just stalls halfway. If an automated workflow genuinely needs to uninstall packages, don’t assume it will sail through: dry-run the command against the real guards with hermes approvals test first, see which verdict it lands on, and then decide whether to allow it, reshape the workflow, or keep the agent away from that step entirely. How approvals behave in cron contexts is covered more fully in the unattended approvals guide.
One distinction to keep straight: dangerous-command approval and approvals.deny are two different mechanisms. The former is the built-in pattern table and works with no configuration from you; the latter is a predefined deny list you maintain yourself. This article is only about the first. For the second, see smart approvals setup — no need to repeat it here.
Finally, the release status, stated plainly: this landed on main on 2026-09-12, later than v0.21.2 (released 2026-09-11). So it is not in any release yet — it’s in the mainline, and it reaches you with the next version. To find out what your current build would do, hermes approvals test above is the direct answer; the v0.21.2 release notes are the most recent shipping release before this change.
The short version
- Package-manager uninstalls now go through dangerous-command approval:
npm uninstall -g,pnpm remove,yarn remove,pip uninstall, andbrew uninstallare all covered. - Installs and updates still don’t prompt:
npm update -g,pip install,brew upgrade, andyarn installare unaffected. Approval is reserved for the genuinely irreversible. - The rules are anchored to command position, so
npm uninstallin quoted text isn’t misflagged, and a global option followed by an argument doesn’t defeat them. - The change is merged to
mainand has not shipped in a release yet.