fix(installer): prefer npm over pnpm for the global install - #168
Merged
Conversation
detect_pm picked pnpm whenever it was on PATH, so `curl | sh` took the least predictable global-install path available on the machine. Two real failures on a dev box, both from pnpm rather than threatcrush: ERR_PNPM_UNEXPECTED_VIRTUAL_STORE Unexpected virtual store location [ERROR] The configured global bin directory ".../pnpm/bin" is not in PATH The first is pnpm's versioned global dir - 9 and 10 use `global/5`, 11 uses `global/v11` - so a box with more than one pnpm (a corepack shim resolves a different version per directory) has them fighting over the same tree. The second is pnpm refusing `add -g` until its own bin dir is exported, which a non-interactive `sh` reading no rc file cannot guarantee. There is also a silent variant: the global pnpm-lock.yaml pins the resolved version, so `pnpm add -g` reinstalls the locked release and reports success while handing back something older than `latest`. npm ships with Node and its global install behaves the same everywhere, so it is the right default for an unattended installer. pnpm, yarn and bun stay as fallbacks in that order for machines without npm. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H3GDps1fD6ccfo93B3ePy1
ThreatCrush Security Scan12 finding(s) HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5
Snippets are redacted; ThreatCrush never prints matched credential material. |
ralyodio
added a commit
that referenced
this pull request
Aug 30, 2026
…rade (#169) Preferring npm (#168) fixed which package manager installs, but not what happens on a machine that already had the CLI installed by another one. A prior `pnpm add -g` leaves its own shim on PATH. The installer would put a current build in npm's global root, then report success using whatever `threatcrush --version` resolved to - the old shim. The output reads ✓ ThreatCrush 0.11.3 installed successfully! on a machine that just downloaded 0.11.4, and nothing says why the upgrade did not take. Reported on macOS, reproduced on Linux. Two changes: - remove_stale_global_installs() runs before the install and drops a copy of the package held by any package manager other than the one being used (pnpm, yarn, bun). Every probe is guarded - `set -e` is on, and none of it is worth aborting a working install for. pnpm refusing to run because its global bin dir is not exported is expected and benign here. - warn_if_shadowed() compares the version on PATH against the version actually written to npm's global root, read from the installed package.json so it stays correct offline. On a mismatch it names both versions and the shadowing path, and suggests `hash -r` before `rm`. Verified end to end: seeded a pnpm global install, ran the installer, watched it remove the pnpm copy and land on 0.11.4 via npm. Unrelated pnpm globals on the box were left alone. Both new functions unit-tested for the equal, empty and mismatched cases; shellcheck -s sh is clean. Claude-Session: https://claude.ai/code/session_01H3GDps1fD6ccfo93B3ePy1 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
detect_pminapps/web/public/install.shpicked pnpm whenever it was on PATH. It now picks npm first, with pnpm → yarn → bun as fallbacks for machines without npm.detect_pm() { - if command_exists pnpm; then echo "pnpm" + if command_exists npm; then echo "npm" + elif command_exists pnpm; then echo "pnpm" elif command_exists yarn; then echo "yarn" elif command_exists bun; then echo "bun" - elif command_exists npm; then echo "npm" else echo ""; fi }Why
curl -fsSL https://threatcrush.com/install.sh | shwas taking the least predictable global-install path available on the box. Two real failures, both pnpm's, neither anything to do with threatcrush:~/.local/share/pnpm/global/5, pnpm 11 usesglobal/v11, and pnpm 10 refuses to reuse aglobal/5tree that pnpm 9 laid out because it moved the virtual store. A box with more than one pnpm has them fighting over the same tree — and that is easy to end up with, because a corepackpnpmshim resolves a different version per directory from each repo'spackageManagerfield. (This very repo pinspnpm@10.33.0, while the same shim serves 11.18.0 from$HOME.)add -guntil its own bin dir is exported. A non-interactiveshfromcurl | shsources no rc file, so the installer cannot guarantee that.pnpm-lock.yamlpins the resolved version, sopnpm add -gcan reinstall the locked release and exit 0 while handing back something older thanlatest. Reproduced:pnpm viewreported0.11.4,pnpm add -ginstalled0.11.3, no warning.npm ships with Node and its global install behaves the same everywhere, which is what an unattended installer wants. The mise bootstrap path is unaffected — it installs Node, which brings npm.
Testing
sh -n apps/web/public/install.sh— syntax clean.detect_pmexercised against stubbedcommand_existsfor four cases: real box →npm; npm absent →pnpm; only bun →bun; nothing →"".detect_pmexists in exactly one file, and that the deployedthreatcrush.com/install.shis byte-identical to repo HEAD, so this file is the single source.next build.🤖 Generated with Claude Code
https://claude.ai/code/session_01H3GDps1fD6ccfo93B3ePy1