bunx: stop passing --force to the spawned install on every dist-tag run
Closed
RO
1mo ago+487+487+012345678901234567890123456789-213-213-012345678901234567890123456789
Problem
bunx <pkg>@latestspawnsbun add <pkg>@latest --no-cache --forceon every run (bunx_command.rs:1341on main).--forcelinks every cached package again. On Windows that costs 30 s or more.- A bare
bunx <pkg>pays the same on every run after the first day: the age check (bunx_command.rs:1123) reads the hardlinked install cache file, whose mtime no install renews.
Fix
- The spawned install passes
--forcein two cases only: the cached binary is not owned by the current user, or a first install without--forceleft no bin to run. Then bunx installs once more with--forceand looks again. A healthy warm run runs one install, without--force. - Read the age of a tree from its
.binentry (lstat). Every install creates it again. - In the install bunx spawns (
BUN_INTERNAL_BUNX_INSTALL), the bin linker (src/install/bin.rs) removes an existing.binentry whose target file is gone. On Windows that entry is a shim that runs on its own, so without this the lookup found a bin and the forced install never ran. A project install keeps the entry: a build script can still create its target. - Correct because
--no-cachefetches the manifest again, sobun add <pkg>@<tag>installs the version the tag names (#4981). A tree that an interrupted install left without some files still heals: the second, forced install links every package again. - Verified:
test/cli/install/bunx.test.ts, blockbunx cache(5 tests). Run on Linux against this change and against main.
Background
- bunx keeps each package in
<temp>/bunx-<uid>-<pkg>@<version>. For a dist-tag it spawnsbun addthere on every run. --no-cachedisables the manifest cache.--forcealso installs packages whose version already matches. Without it the installer keeps a package whosepackage.jsonname and version match, even when other files are missing.- Considered a forced install every 24 hours (a tool used once a day pays on every run) and an error that names the directory to remove (the user repairs by hand what main repaired on its own).
Downsides
- A package whose bin names a file it does not ship runs two installs per run instead of one before it fails.
- A bare
bunx <pkg>on Linux and macOS followslatestonce per 24 hours, not on every run after day one. - Binary: +256 bytes text, +0 data at ead653cbf6 (
llvm-size, release, main 2722608f47). Not measured again for the later commits.
Notes
Measured on Linux x64, release builds, hardlink backend, loopback registry. The tree is one tool with 20 dependencies (21 packages, 83 files). The counts are syscalls of the spawned bun add (ptrace counter). The builds are main 2722608f47 and 00582846d1.
| Run | Build | linkat | renameat | unlinkat | Registry requests |
|---|---|---|---|---|---|
warm bunx tool@latest | main | 83 | 22 | 127 | 1 |
warm bunx tool@latest | this PR | 0 | 1 | 2 | 1 |
bare bunx tool, tree 25 h old, runs 1 / 2 / 3 | main | 83 / 83 / 83 | 1 / 1 / 1 | ||
bare bunx tool, tree 25 h old, runs 1 / 2 / 3 | this PR | 0 / 0 / 0 | 1 / 0 / 0 |
- Earlier measurements on a 16 vCPU Windows VM with no antivirus, canary bun: warm
bun x shadcn@latest --help2.2 s vs 1.0 s on Linux. The spawned install in the warm cache dir:--no-cache --force1.61 s,--no-cachealone 0.07 s. The cost is the forced re-link of 252 packages, which Defender amplifies on user machines. - On Windows the age check already reads the
.binshim, which every install writes again. There a stale bare-name run was one forced install per 24 hours. It is now one plain install per 24 hours. - The tag still moves at once in both directions. The test moves
latestfrom 1.0.0 to 1.1.0 and back. - A package that declares no
bingets one install and the error: a forced install cannot add a bin. - The retry: the install and the two bin probes now sit in a loop that runs at most twice. The loop re-indents that block, so most of the diff in
bunx_command.rsis whitespace.git diff -wshows the change itself (about 26 lines). - A file that another local user plants is a different case. bunx refuses a cache directory that is not owned by the current user or that group or others can write (
is_trusted_cache_root). - Not changed: the second age check (
get_bin_name_from_temp_directory, rootpackage.jsonmtime). For a package whose bin is not named after the package, a bare-name or pinned run still deletes the tree once per 24 hours. - The tool no longer inherits
BUN_INTERNAL_BUNX_INSTALLfrom the spawned install. On main that variable only changed argv dispatch, so the leak was harmless. Now the bin linker reads it, and an install the tool spawns in the user's project (shadcn runsbun add) must not. A test runs a bin that prints the variable. - The Windows CI lanes run the tests. The two retry tests and the marker test fail on the branch head before the retry commit and on main.
no test proof · iteration 1 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/install/bunx.test.ts