Repository navigation
Redirect pnpm, bun, deno, and gem global installs to ~/.local per the inbox#584 decision - #659
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: gominimal/pkgs/.coderabbit.yaml Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (8)
Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour. 📝 WalkthroughWalkthroughBun, Deno, and pnpm now install executables under ChangesUser-local Installation Defaults
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The user-local installation defaults have no identified merge-blocking issue. Merge after normal build and package checks pass. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
pnpm add -g work in sessions via stack-level PNPM_HOMEpnpm add -g work in sessions by defaulting PNPM_HOME in the pnpm package wrappers
pnpm add -g work in sessions by defaulting PNPM_HOME in the pnpm package wrapperspnpm add -g and bun add -g work in sessions via PNPM_HOME/BUN_INSTALL-defaulting wrappers
pnpm add -g and bun add -g work in sessions via PNPM_HOME/BUN_INSTALL-defaulting wrappers|
CI failure post-mortem (both failures were mine, both fixed in d5142f9):
@bryan thanks for the re-trigger — my fix is rebased on top of your no-op, so the new run includes both. The 2h bun-build hang looks unrelated to the diff content (the bun build.sh changes only touch the install step after |
|
Heads-up: this now conflicts with main ( Also worth saying: the last CI run here completed 2026-08-28, so its red is stale and predates everything below. The one that matters: don't restore
|
pnpm refuses global installs when its derived global bin dir (~/.local/share/pnpm/bin) is not on PATH, and the session PATH is fixed, so `pnpm setup` has nothing to edit. pnpm 11 honors no npm_config_*/ pnpm_config_* env spellings for this; the one lever is PNPM_HOME, whose bin/ subdir becomes the global bin dir. Pointing it at ~/.local lands global bins in /home/.local/bin, which sessions already have on PATH. Stack build_env_vars fold into session composition with session-declared vars taking precedence, so users can still override. Refs gominimal/inbox#584 (follow-up to gominimal/inbox#559) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
The stack leverages the pnpm package, so defaulting PNPM_HOME in the
package's /usr/bin wrappers covers every consumer — stack-composed
projects and bare [session].packages entries alike — where the stack
env var only reached projects with a [stack] block. The wrappers use
`: "${PNPM_HOME:=$HOME/.local}"`, so a caller's own PNPM_HOME still
wins, and $HOME replaces the stack variant's hardcoded /home.
Refs gominimal/inbox#584
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
The previous commit's message covered the whole stack-to-package move but only captured the stack revert; this adds the other half: replace the /usr/bin/pnpm and /usr/bin/pnpx symlinks with wrappers defaulting PNPM_HOME to ~/.local (caller's PNPM_HOME wins), plus a global_bin_dir regression test. Refs gominimal/inbox#584 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
Same shape as the pnpm fix: bun's derived global location (cache-derived, e.g. /state/cache/.bun) keeps `bun add -g` bins off PATH. Move the real binary to /usr/libexec/bun and make /usr/bin/bun and /usr/bin/bunx wrappers that default BUN_INSTALL to ~/.local, so global bins land in ~/.local/bin (on the session PATH); a caller's BUN_INSTALL wins. bunx stays an argv0 symlink beside the real binary, so bun's name-based multiplexing is preserved (verified live). The standalone smoketest now targets the raw ELF, since a #!/bin/sh wrapper cannot run in the shell-less standaloneTest sandbox. Refs gominimal/inbox#584 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
deno: same wrapper shape as pnpm/bun — the real binary moves to /usr/libexec/deno and /usr/bin/deno defaults DENO_INSTALL_ROOT to ~/.local, so `deno install -g` bins land in ~/.local/bin (on the session PATH) instead of ~/.deno/bin; a caller's own value wins. The standalone smoketest targets the raw ELF (no shell in that sandbox for a #!/bin/sh wrapper). gem: RubyGems' system config path is /usr/etc/gemrc — inside the package — so ship one with `gem: --bindir ~/.local/bin`. gem already falls back to user installs on the read-only store; this moves only the binstubs onto the session PATH. Verified: ~ expands, the flag is harmless for non-install commands, and uninstall removes the stub symmetrically. User config (GEMRC, ~/.gemrc, CLI) still wins. Also mark the bun and deno libexec outputs allow_executable — a Data output matching a binary fails `min check` without it. Refs gominimal/inbox#584 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The #!/bin/sh wrappers made pnpm, bun, and deno need a runtime provider for bin/sh — declare bash (which ships the sh symlink) in each package's runtime_deps, per the missing-runtime-deps checker. The ruby package's new system gemrc (binstubs default to ~/.local/bin) leaked into ruby-lsp's vendored build: its `gem install` picked up the default and wrote the binstub into the build sandbox home instead of $GEMS/bin, so /usr/bin/ruby-lsp exec'd a missing path. Pass --bindir explicitly there — CLI flags beat gemrc — keeping the vendored install self-contained. ruby-lsp is the only build-time `gem install` in the catalog. Refs gominimal/inbox#584 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
0ba3ed7 to
9abf7d0
Compare
|
Rebased onto main ( Conflicts, as resolved
On
|
pnpm moved to the 12.5.1 native binary on main. Keep that layout and carry the PNPM_HOME wrapper over it: every /usr/bin entry (pnpm, pn, pnpx, pnx) is now a wrapper that execs its /usr/libexec/pnpm counterpart by real path, the ELF test points at the binary in libexec, and the global_bin_dir test is kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pnpm 12 reads PNPM_CONFIG_GLOBAL_BIN_DIR and uses it in place of the <pnpm home>/bin it would otherwise derive. Defaulting that alone lands `pnpm add -g` bins in ~/.local/bin while the store and global package dir stay at pnpm's own defaults. A caller's PNPM_CONFIG_GLOBAL_BIN_DIR or PNPM_HOME still wins. See gominimal/inbox#584. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bun 1.3.14 reads BUN_INSTALL_BIN for the global bin symlinks and BUN_INSTALL_GLOBAL_DIR for the package dir they point into. The wrapper defaults both: without the second, the global dir falls back under $XDG_CACHE_HOME and clearing the cache leaves the ~/.local/bin links dangling. A caller's value of either, or a caller's BUN_INSTALL, wins. See gominimal/inbox#584. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RubyGems merges config files key by key, so a user's own `gem: --no-document` replaced the shipped `gem:` line and dropped the bindir. Set --bindir on install, update and uninstall instead. Bundler ignores these keys; its executables stay off PATH (out of scope here). See gominimal/inbox#584. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With HOME unset the pnpm, bun and deno wrappers would have exported paths under /.local. Leave the variables alone then, so each tool falls back to its own resolution. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each package's test now installs a local package or script globally (pnpm add -g, bun add -g, deno install -g, gem install --local) and asserts the executable resolves from ~/.local/bin on a session-like PATH and runs, instead of probing the env var. pnpm and bun also check that a caller's override still wins, pnpm that its store stays out of ~/.local, and gem that a user `gem:` line keeps the bindir and that uninstall removes the stub. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
/review |
…ak each other (gominimal#671) * fix(pnpm): depend on node, not node-lts — two npms in one sandbox breaks both pnpm only needs *a* node for its `#!/usr/bin/env node` shebang, but whichever one it names enters the closure of every package built with pnpm. `next` and `agent-browser` both declare `node` themselves, so each sandbox held TWO npm installs at the same paths. The sandbox hardlinks closure members first-writer-wins PER FILE, so /usr/lib/node_modules/npm came out a blend: minipass-flush/index.js from npm 11.17.0 (named `{ Minipass }` import) beside a nested minipass@3.3.6 from npm 11.11.1 (default export only). That tree never existed upstream and cannot load — `class Flush extends undefined` throws and every `npm install` dies. It broke `next` for two days and blocked gominimal#665, gominimal#667, gominimal#669 and gominimal#659. Verified: closure of next 75->74 and agent-browser 114->113 with node-lts gone, capy (the only other node-lts consumer) untouched; next, pnpm and agent-browser each build --rebuild clean and pass min check 15/15. Supersedes gominimal#670, whose fix was a no-op against node-lts's own output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refine: take only the node BINARY, not the whole package Review point from Bryan: pnpm should not be dictating a node version to its consumers at all. The spec language cannot express that today — RuntimeDep is BuildSpec | Subset | UpstreamPkg, all concrete, with no "consumer provides it" form — but `subsetOf` gets most of the way there. pnpm needs a node to interpret `#!/usr/bin/env node` and nothing else. Taking `subsetOf node ["node"]` instead of the whole package means pnpm can never contribute an npm tree to any closure, so the two-npms-merged-per-file failure is structurally impossible rather than merely absent from today's fleet. Residual, and worth stating: pnpm still names a version, so a future node-lts consumer adding pnpm would collide on the `node` binary. That is a much smaller failure than an unloadable module graph, and the real fix is a virtual/provided dependency in the spec language. Verified with the subset: next, pnpm and agent-browser all build --rebuild clean and pass min check, including pnpm's `missing runtime_deps` — which independently confirms the bare binary satisfies the shebang. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
/review |
Implements the decision recorded in gominimal/inbox#584 for four ecosystems — follow-up to gominimal/inbox#559, whose npm half is #657: global installs redirect to
~/.localvia each tool's narrowest honored knob, delivered at the package level. The failure modes below were observed in a live session.What breaks today
global bin directory ".../pnpm/bin" is not in PATH(PATH is fixed;pnpm setuphas nothing to edit)bun add -gsucceeds into a cache-derived dir whosebinis off PATH — binaries never resolvedeno install -gtargets~/.deno/bin, off PATH; deno's own hint ("Add ... to PATH") is impossible to follow~/.local/share/gem/ruby/<v>/bin, off PATHThe fixes
/usr/binwrappers defaultPNPM_CONFIG_GLOBAL_BIN_DIRto$HOME/.local/bin. pnpm 12.5.1 reads it from the env (crates/config/src/env_overlay.rs) and uses it in place of<pnpm home>/bin(derive_layout.rs), so only the bin dir moves; the store and global package dir stay at pnpm's defaults. A caller's ownPNPM_CONFIG_GLOBAL_BIN_DIRorPNPM_HOMEwins.BUN_INSTALL_BIN=$HOME/.local/binandBUN_INSTALL_GLOBAL_DIR=$HOME/.local/share/bun/global(bun 1.3.14PackageManagerOptions.zig). Both are needed: without the second, the global dir falls back under$XDG_CACHE_HOMEand clearing the cache leaves the~/.local/binlinks dangling. A caller's value of either, or a caller'sBUN_INSTALL, wins.DENO_INSTALL_ROOT=$HOME/.local; deno installs into<root>/bin.HOMEis unset, and exec the real entry point from/usr/libexec/<pkg>/./usr/etc/gemrc, inside the package, so the ruby package ships one withinstall:,update:anduninstall:keys each set to--bindir ~/.local/bin. Per-command keys rather than agem:line because RubyGems merges config files key by key, so a user's owngem: --no-documentwould have dropped the bindir.GEMRC/~/.gemrc/CLI still override. Bundler ignores these keys, sobundle installexecutables stay off PATH (out of scope).globalBinDirin pnpm's config orglobalBinDir/globalDirin bunfig is overridden by the wrapper default; setting the env var (orPNPM_HOME/BUN_INSTALL) is the way to choose another location.libexecoutputs carryallow_executable = true(a Data output matching a binary failsmin checkwithout it), and their standalone smoketests target the raw ELFs sincestandaloneTestruns without a shell.bunxstays an argv0 symlink beside the real binary, preserving name-based multiplexing (verified live through an exec'd wrapper).Not included, deliberately: go (finds
GOROOTrelative to its own executable, and half the catalog builds with it — needs its own PR and verification) and cargo (blocked on whetherrustsessions should link at all —cc/ldare absent today). Both tracked in gominimal/inbox#584.min task runsandboxes have no~/.local/binon PATH, so global installs made inside a task remain out of scope; gominimal/inbox#584 tracks that.An earlier revision did the pnpm half as a stack-level
build_env_varsentry; reverted in favor of package delivery (covers stack-less consumers,$HOMEnot a literal/home). History carries both steps; squash-merge collapses them.Verification
The CI test blocks are the verification for the current revision. Each one installs something globally, offline, and asserts that
PATH=/usr/bin:/bin:$HOME/.local/bin command -v <bin>resolves to~/.local/bin/<bin>and runs:global_install):pnpm add -gof a localfile:package with abin; also assertspnpm store pathis not under~/.local/store, and thatPNPM_CONFIG_GLOBAL_BIN_DIRandPNPM_HOMEset by the caller still decidepnpm bin -g.global_install):bun add -gof the same kind of package; also asserts the global dir is~/.local/share/bun/global, and that a caller'sBUN_INSTALLsuppresses the defaults.global_install):deno install -g -n gbinof a local script.gem_global_install):gem build+gem install --localof a minimal gem with an executable, with a user~/.gemrccarryinggem: --no-document; thengem uninstall -xremoves the stub.Standalone tests run with the build-sandbox env (
HOME=/state/home, no~/.local/binon PATH), so each test spells out a session-like PATH.Outside CI: the pnpm, bun and deno test bodies were run locally against the upstream 12.5.1 / 1.3.14 / 2.9.5 release binaries behind the generated wrappers (pnpm and deno ship those exact tarballs; the bun package builds from source, so its binary differs), with a fresh
HOME, and pass; the pnpm test fails against the previousPNPM_HOMEwrapper. The ruby test has not been run outside CI.Live Minimal session (2026-10-02, this revision): all four stacks pass on macOS 26.6.2 / Apple Silicon with min 0.6.0-dev.78 (minvmd backend, guest kernel 6.12.105 aarch64), in a session pinned to
locked_commit = 7fb6d21don this branch with packagesbase, pnpm, bun, deno, ruby, ruby-lsp, activated with--no-loadouts, a freshHOME, and~/.local/binremoved before each stack. The wrappers at/usr/bin/{pnpm,bun,deno}were byte-identical to this PR's heredocs and/usr/etc/gemrccarried the three--bindirlines.pnpm add -g file:…→~/.local/bin/gbin-pnpm, runs;pnpm bin -g→~/.local/bin; store at~/.local/share/pnpm/store/v11, nothing under~/.local/store;PNPM_CONFIG_GLOBAL_BIN_DIRandPNPM_HOMEfrom the caller each decidepnpm bin -g.bun add -g→~/.local/bin/gbin-bun(symlink into~/.local/share/bun/global), runs;$XDG_CACHE_HOME/.bun/install/globalnot created;BUN_INSTALLmoves both the global dir and the bin dir;bunx --versionand an exec'd/usr/bin/bunxboth answer.deno install -g -n gbin→~/.local/bin/gbin, runs;~/.denonot created;DENO_INSTALL_ROOTfrom the caller wins.gem envmerges the system--bindirlines with a user~/.gemrcofgem: --no-document;gem build+gem install --local→~/.local/bin/gbin-gem, runs, no docs generated;gem uninstall -xleaves~/.local/binempty; aGEMRCoverride redirects the stub.ruby-lsp --versionanswers and a JSON-RPCinitializeround-trip returns capabilities; its vendored bins stay under/usr/lib/ruby-lsp/gems/bin.Observed and unrelated to this PR: bun's
addbanner reports1.3.14-canary.1whilebun --versionreports1.3.14(source-build version string); the ruby-lsp launcher composes a bundle on first start and self-updates to 0.26.11 from the network; pnpm prints its update nag. shellcheck: lines added to the build scripts and the generated wrappers are clean; pre-existing findings are untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01JGTXbdK7hgDupktNZEkALY
Summary by CodeRabbit
~/.localfor global installation locations when the relevant settings are unset.~/.local/binwhenPNPM_HOMEis unset.~/.local/binby default.