fix: warn before npm prefix redirection (#73890) (thanks @Sayeem3051)

This commit is contained in:
Peter Steinberger
2026-04-30 04:39:33 +01:00
parent bbf932fd7d
commit 5f13af6b68
3 changed files with 57 additions and 0 deletions

View File

@@ -286,6 +286,7 @@ Docs: https://docs.openclaw.ai
- Commands: keep channel-prefixed owner allowlist entries scoped to matching providers so webchat command contexts cannot inherit external channel owners. Thanks @zsxsoft.
- Auth/device pairing: bound bootstrap handoff token issuance, redemption, and approved pairing baselines to the documented per-role scope allowlist, so bootstrap approvals cannot persistently grant `operator.admin`, `operator.pairing`, or `node.exec` scopes. Thanks @eleqtrizit.
- Providers/GitHub Copilot: support the GUI/RPC wizard device-code auth flow so onboarding from non-TTY clients (gateway RPC bridge, GUI wizards) completes instead of returning empty profiles. Dangerous-state handling now distinguishes `access_denied` and `expired_token` from transport errors. (#73290) Thanks @indierawk2k2.
- Installer/Linux: warn before switching an unwritable npm global prefix to `~/.npm-global`, then tell users to run future global updates with `npm i -g openclaw@latest` without `sudo` so npm keeps using the redirected user prefix. Fixes #44365; carries forward #50479. Thanks @Sayeem3051.
## 2026.4.27

View File

@@ -1633,9 +1633,12 @@ fix_npm_permissions() {
return 0
fi
ui_warn "npm global prefix is not writable: ${npm_prefix}"
ui_warn "The installer will switch npm's user prefix to ${HOME}/.npm-global; npm normally writes that setting to ~/.npmrc."
ui_info "Configuring npm for user-local installs"
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
ui_warn "Avoid sudo npm i -g for future OpenClaw updates; use npm i -g openclaw@latest so npm keeps using this user prefix instead of a different global prefix."
# shellcheck disable=SC2016
local path_line='export PATH="$HOME/.npm-global/bin:$PATH"'

View File

@@ -108,6 +108,59 @@ describe("install.sh", () => {
expect(output).toContain(`path=${nvmNode}`);
expect(output).toContain("version=v22.22.1");
});
it("warns before redirecting an unwritable npm prefix", () => {
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-npm-prefix-"));
const home = join(tmp, "home");
const events = join(tmp, "events.log");
mkdirSync(home, { recursive: true });
let result: ReturnType<typeof runInstallShell> | undefined;
try {
result = runInstallShell(`
set -euo pipefail
source "${SCRIPT_PATH}"
OS=linux
HOME=${JSON.stringify(home)}
prefix=${JSON.stringify(join(tmp, "root-owned-prefix"))}
events=${JSON.stringify(events)}
npm() {
if [[ "$1" == "config" && "$2" == "get" && "$3" == "prefix" ]]; then
printf '%s\\n' "$prefix"
return 0
fi
if [[ "$1" == "config" && "$2" == "set" && "$3" == "prefix" ]]; then
printf 'npm-set:%s\\n' "$4" >> "$events"
return 0
fi
return 1
}
ui_info() { printf 'info:%s\\n' "$*" >> "$events"; }
ui_warn() { printf 'warn:%s\\n' "$*" >> "$events"; }
ui_success() { printf 'success:%s\\n' "$*" >> "$events"; }
fix_npm_permissions
cat "$events"
`);
} finally {
rmSync(tmp, { force: true, recursive: true });
}
expect(result?.status).toBe(0);
const lines = (result?.stdout ?? "").trim().split("\n");
const warningIndex = lines.findIndex((line) =>
line.includes("The installer will switch npm's user prefix"),
);
const npmSetIndex = lines.findIndex((line) => line.startsWith("npm-set:"));
const noSudoWarningIndex = lines.findIndex((line) => line.includes("Avoid sudo npm i -g"));
expect(warningIndex).toBeGreaterThanOrEqual(0);
expect(npmSetIndex).toBeGreaterThan(warningIndex);
expect(noSudoWarningIndex).toBeGreaterThan(npmSetIndex);
expect(result?.stdout).toContain("npm global prefix is not writable");
expect(result?.stdout).toContain("npm normally writes that setting to ~/.npmrc");
expect(result?.stdout).toContain("npm i -g openclaw@latest");
expect(result?.stdout).toContain("using this user prefix");
expect(result?.stdout).not.toContain("has been saved");
});
});
describe("install.sh macOS Homebrew Node behavior", () => {