fix(coding-agent): skip npm audit and fund output when installing pi packages (#11469)

`pi install`, `pi remove` and `pi update --extensions` run npm without
`--no-audit`, so npm prints its "N vulnerabilities … run `npm audit
fix`" advice after every install. pi gives users no way to clear that
warning, and following it causes harm. In `~/.pi/agent/npm`, `npm audit
fix` runs without `--legacy-peer-deps`, so npm installs the
host-provided `@earendil-works/pi-*` peers there: a second copy of pi.
The comment above `getNpmInstallArgs` warns against exactly that.

The change adds `--no-audit --no-fund` wherever pi already passes
`--legacy-peer-deps` to npm:
- `getNpmInstallArgs` (npm package installs and updates)
- `getGitDependencyInstallArgs` (dependency installs for git packages)
- npm uninstall

pi's managed self-update (`runManagedNpmCi`) already passes both flags.
bun and pnpm arguments are unchanged.

I did not edit `CHANGELOG.md`, per CONTRIBUTING.md.

**Testing**
- Updated the expected npm argument arrays in
`test/package-manager.test.ts`. With the source change reverted, 11
tests fail; with it, all 132 pass.
- `npm run check` passes.
- Ran the built CLI by hand with `PI_CODING_AGENT_DIR=/tmp/pa pi install
npm:lodash@4.17.20`:
- Before the change, the output ended with npm's "To address all issues,
run: npm audit fix" block.
- After, it shows only "added 1 package" and "Installed
npm:lodash@4.17.20".

Fixes #11409

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
This commit is contained in:
robopipi[bot] 2026-10-27 09:17:54 +01:00 • committed by GitHub
parent deb12d78eb
commit 5fb9a0b110
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 22 deletions

View file

@ -1858,7 +1858,7 @@ export class DefaultPackageManager implements PackageManager {
"--config.strict-dep-builds=false",
];
case "npm":
return ["install", "--omit=dev", "--legacy-peer-deps"];
return ["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"];
default:
return ["install"];
}
@ -1891,7 +1891,8 @@ export class DefaultPackageManager implements PackageManager {
"--config.strict-dep-builds=false",
];
}
return ["install", ...specs, "--prefix", installRoot, "--legacy-peer-deps"];
// npm audit fix would run without --legacy-peer-deps and install pi peers, so skip audit advice.
return ["install", ...specs, "--prefix", installRoot, "--legacy-peer-deps", "--no-audit", "--no-fund"];
}
private async installNpm(source: NpmSource, scope: SourceScope, temporary: boolean): Promise<void> {
@ -1914,7 +1915,7 @@ export class DefaultPackageManager implements PackageManager {
}
const args = ["uninstall", source.name, "--prefix", installRoot];
if (packageManagerName !== "pnpm") {
args.push("--legacy-peer-deps");
args.push("--legacy-peer-deps", "--no-audit", "--no-fund");
}
await this.runNpmCommand(args);
}

View file

@ -756,6 +756,8 @@ Content`,
"--prefix",
realpathSync(join(agentDir, "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
],
undefined,
);
@ -769,7 +771,15 @@ Content`,
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["uninstall", "@scope/pkg", "--prefix", realpathSync(join(agentDir, "npm")), "--legacy-peer-deps"],
[
"uninstall",
"@scope/pkg",
"--prefix",
realpathSync(join(agentDir, "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
],
undefined,
);
});
@ -793,12 +803,12 @@ Content`,
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "@scope/pkg", "--prefix", realNpmRoot, "--legacy-peer-deps"],
["install", "@scope/pkg", "--prefix", realNpmRoot, "--legacy-peer-deps", "--no-audit", "--no-fund"],
undefined,
);
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["uninstall", "@scope/pkg", "--prefix", realNpmRoot, "--legacy-peer-deps"],
["uninstall", "@scope/pkg", "--prefix", realNpmRoot, "--legacy-peer-deps", "--no-audit", "--no-fund"],
undefined,
);
});
@ -849,9 +859,13 @@ Content`,
await packageManager.install(source);
expect(runCommandSpy).toHaveBeenCalledWith("npm", ["install", "--omit=dev", "--legacy-peer-deps"], {
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"],
{
cwd: targetDir,
},
);
});
it("should remove a newly created checkout when git clone fails", async () => {
@ -915,9 +929,13 @@ Content`,
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith("git", ["clean", "-fdx"], { cwd: targetDir });
expect(runCommandSpy).toHaveBeenCalledWith("npm", ["install", "--omit=dev", "--legacy-peer-deps"], {
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"],
{
cwd: targetDir,
},
);
});
it("should reconcile an existing git checkout to its update target when installing without a ref", async () => {
@ -1066,9 +1084,13 @@ Content`,
await packageManager.update(source);
expect(runCommandSpy).toHaveBeenCalledWith("npm", ["install", "--omit=dev", "--legacy-peer-deps"], {
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"],
{
cwd: targetDir,
},
);
});
it("should repair missing git package dependencies when the checkout is already current", async () => {
@ -1093,9 +1115,13 @@ Content`,
await packageManager.update(source);
expect(runCommandSpy).toHaveBeenCalledWith("npm", ["install", "--omit=dev", "--legacy-peer-deps"], {
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"],
{
cwd: targetDir,
},
);
expect(runCommandSpy).not.toHaveBeenCalledWith("git", ["clean", "-fdx"], { cwd: targetDir });
});
@ -1127,9 +1153,13 @@ Content`,
await expect(packageManager.update(source)).rejects.toThrow("simulated clean failure");
expect(runCommandSpy).toHaveBeenCalledWith("npm", ["install", "--omit=dev", "--legacy-peer-deps"], {
cwd: targetDir,
});
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "--omit=dev", "--legacy-peer-deps", "--no-audit", "--no-fund"],
{
cwd: targetDir,
},
);
});
it("should disable peer installation through wrapped pnpm when updating git dependencies", async () => {
@ -2506,7 +2536,15 @@ export default function(api) { api.registerTool({ name: "test", description: "te
);
expect(runCommandSpy).toHaveBeenCalledWith(
"npm",
["install", "example@^1.0.0", "--prefix", realpathSync(join(tempDir, ".pi", "npm")), "--legacy-peer-deps"],
[
"install",
"example@^1.0.0",
"--prefix",
realpathSync(join(tempDir, ".pi", "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
],
undefined,
);
});
@ -2572,6 +2610,8 @@ export default function(api) { api.registerTool({ name: "test", description: "te
"--prefix",
realpathSync(join(agentDir, "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
]);
mkdirSync(managedPath, { recursive: true });
writeFileSync(
@ -2689,6 +2729,8 @@ export default function(api) { api.registerTool({ name: "test", description: "te
"--prefix",
realpathSync(join(agentDir, "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
],
undefined,
);
@ -2702,6 +2744,8 @@ export default function(api) { api.registerTool({ name: "test", description: "te
"--prefix",
realpathSync(join(tempDir, ".pi", "npm")),
"--legacy-peer-deps",
"--no-audit",
"--no-fund",
],
undefined,
);