Skip to content

auto-install: keep the fallback cache in .bun-cache, not in node_modules - #45610

Draft
robobun wants to merge 2 commits into
mainfrom
robobun/47d676a8/autoinstall-cache-fallback
Draft

robobun wants to merge 2 commits into
mainfrom
robobun/47d676a8/autoinstall-cache-fallback

Conversation

@robobun

@robobun robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • With no usable install cache directory (no HOME, a directory that cannot be made, [install.cache] disable = true), a script that imports a package works once. Later runs fail with error: Cannot find package 'foo' from '<script>'.
  • The cache falls back to node_modules/.bun-cache or node_modules/.cache (ensure_cache_directory, src/install/PackageManager/PackageManagerDirectories.rs). A node_modules directory turns auto-install off.

Fix

  • The runtime falls back to .bun-cache in the working directory. bun install is unchanged.
  • The bun commands that skip node_modules skip .bun-cache too: fmt, lint, run --filter, pm pack, pm diff, create, the framework router.
  • Self-reviewed: 10 concerns raised, 0 addressed yet. All of them wait for one decision: is a visible .bun-cache acceptable? See the comment below.
  • Verified: 22 new cases in test/cli/run/run-autoinstall-cache-dir.test.ts and test/bake/framework-router.test.ts. All fail on main.

Background

  • Auto-install: with no node_modules directory at or above a script, bun installs imported packages at run time into its cache.
  • Considered a resolver rule that ignores a node_modules with only bun's cache: bun still creates node_modules. Considered the temp directory: cached packages resolve from <tmp>/node_modules, which other users can write.

Downsides

  • .bun-cache is visible beside the script. Prettier, ESLint and git status see it. They skipped node_modules.
  • The error comes back after bun pm cache, which still makes node_modules/.bun-cache. A node_modules/.bun-cache from an older bun also keeps auto-install off.
  • Release .text grows by at most 2,048 bytes (see Notes). bun pm pack tests one more name per entry: no time change above noise.
Notes

Who hits it. Containers and CI where HOME is unset, / or read-only, and anyone with [install] cache = false or [install.cache] disable = true. The same happens when BUN_INSTALL_CACHE_DIR names a file, a name of 300 letters, or a path below /proc.

In one process. After the first import makes node_modules, any failed resolve makes the resolver read the directory again. Later imports then fail with Cannot find module.

Reproduction. A directory with index.js (import "no-deps") and a bunfig.toml that names a local registry. Run env -u HOME -u BUN_INSTALL -u BUN_INSTALL_CACHE_DIR -u XDG_CACHE_HOME bun index.js two times. On main the first run loads the package and makes node_modules/.bun-cache. The second run fails and sends 0 requests. With this change, three runs load the package, the registry gets 2, 0 and 0 requests, and the directory holds .bun-cache.

Measurements. Release builds of main a9c57bdd89 and of this change on top of it, linux x64. The measured build also made the failure to open the cache directory a resolve error. #45307 has since done that on main and this diff no longer has it, so the size is an upper bound. The counts are libc calls logged by an LD_PRELOAD shim (mkdir, mkdirat, open*, readlink*, renameat, symlinkat).

  • mkdirat(<cwd>/node_modules) during a runtime import: 1 on main, 0 here, in each of 3 cases (no HOME, HOME below a file, cache = false).
  • mkdirat calls to make the fallback cache: 3 on main, 1 here. Logged calls of the first run: 16 on main, 14 here.
  • A usable cache directory (HOME set): the logs of run 1 (18 calls) and run 2 (6 calls) are identical on both builds.
  • bun install on its own fallbacks (no HOME, and cache = false): 16 calls each, identical logs. node_modules still holds .bun-cache or .cache.
  • A warm auto-install resolve: 2,000 and 4,000 calls of require.resolve("no-deps") log 7 calls on both builds. The changed function, ensure_cache_directory, is #[cold] and runs once per process. Instructions were not counted: perf and valgrind are not installed here.
  • bun pm pack --dry-run, 1,001 files, 40 interleaved runs: median 22.3 ms here, 19.6 ms and 24.0 ms for two series of main.
  • Section sizes (size -A): .text 80,756,901 to 80,758,949 bytes. .rodata is unchanged.

On top of #44968. fetch_cache_directory_path still returns node_modules/.bun-cache with is_cwd_fallback set. The runtime replaces the path when that flag is set. bunx keeps its own rule for the flag.

After #45307. That pull request made the process exits of the cache and temp directories (bun is unable to write files) errors of the import. This change does not touch them. When .bun-cache cannot be made, the import fails with that error.

Not in this change.

  • bun pm cache with no named cache directory still makes node_modules/.bun-cache, and auto-install is then off again (see below). bun pm cache rm does not clear .bun-cache.
  • A node_modules that holds only the cache of another tool (prettier --cache writes node_modules/.cache/prettier) turns auto-install off. That is the documented rule.
  • Files loaded from .bun-cache are watched by --watch and --hot, like files from the global cache. Files in node_modules are not.

Open after self-review. Checked on a debug build of this branch unless it says otherwise.

  • The branch conflicts with main. bake: move the code into folders by job (no behavior change) #45489 deleted src/runtime/bake/FrameworkRouter.rs and src/runtime/bake/bake_body.rs, and this diff edits both.
  • Two runs load the package. Then bun pm cache prints and makes <cwd>/node_modules/.bun-cache, and the third run fails with error: Cannot find package 'no-deps'. The review measured the same for bun outdated, bun pm fetch, bun install --lockfile-only and four more commands. I ran bun pm cache only.
  • BUN_INSTALL_CACHE_DIR=node_modules/.bun-cache is replaced by <cwd>/.bun-cache: the runtime recognises the fallback by its spelling (is_cwd_fallback). ./node_modules/.bun-cache is used as named, and the second run fails. So the new docs sentence about BUN_INSTALL_CACHE_DIR is wrong for the first value.
  • Reported by the review, not rerun by me: bun -i with a writable node_modules in a directory that the user cannot write loaded the package before and fails now with EACCES. The second arm no longer uses a node_modules that exists.
  • "Only its name changes" was wrong. Files in .bun-cache are not under node_modules, so they are watched, and they can invoke macros. The global cache has the same property.
  • No user issue reports this failure. The review found none in three tracker searches.

Tests. The 21 cases of run-autoinstall-cache-dir.test.ts include the ones for fmt, lint, run --filter, pm pack, pm diff and create. They are in their own file, and not concurrent: a debug build under load runs some cases of run-autoinstall.test.ts, format.test.ts and lint.test.ts past the default timeout of 5 s. On the two release builds, 0 of 22 and 22 of 22 new cases pass. On a debug build of this branch (main f0e4f6b3b5), 21 of 21 and 36 of 36 pass. That was before #45489 moved the router files, so the router case has not run against main's files.

Other suites, on the same two release builds: run-autoinstall.test.ts, autoinstall-cached-manifest.test.ts, resolve-autoinstall-*.test.ts, run-autoinstall-abs-path.test.ts, bun-pm.test.ts, bun-pack.test.ts, bun-pm-diff.test.ts, bun-create.test.ts, filter-workspace.test.ts, init.test.ts and lint/defaults.test.ts pass on both. format.test.ts, lint.test.ts and lint/config-files.test.ts have cases that time out on this machine on both builds. The cases that failed only with this change pass when they run alone.

bun run rust:check-all x86_64-pc-windows-msvc aarch64-apple-darwin passed for the measured build. CI ran the new file on Windows and macOS x64. On Windows one case is skipped: a process that starts without USERPROFILE gets the variable of its parent, so the cause "no HOME" is POSIX only.

When no install cache directory can be used, the package manager keeps
its cache below the working directory: in node_modules/.bun-cache when
nothing names a directory, in node_modules/.cache when the cache is
off or the named directory cannot be made. `bun install` installs into
node_modules, so that is where its cache belongs.

The runtime's auto-install is on only while no node_modules directory
exists. Its own cache made one. The first run loaded the package, and
every later run failed with "Cannot find package". So did a later
import in the same process, after any failed resolve.

The runtime now keeps that cache in .bun-cache in the working
directory. `bun install` is unchanged.

The commands that walk a project and skip node_modules skip .bun-cache
too: bun fmt, bun lint, bun run --filter, bun pm pack, bun pm diff,
bun create and the framework router.
@robobun

robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 8:05 PM PT - Oct 16th, 2026

@robobun, your commit 04adffa is building: #126971

@robobun

robobun commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Status: draft.

Reproduction on main: a directory with index.js (import "no-deps") and a bunfig.toml that names a local registry. Run bun index.js two times with HOME, BUN_INSTALL, BUN_INSTALL_CACHE_DIR and XDG_CACHE_HOME unset. The first run loads the package and makes node_modules/.bun-cache. The second run fails with error: Cannot find package 'no-deps' and sends 0 requests to the registry.

With this branch, three runs load the package. The registry gets 2, 0 and 0 requests, and the directory holds .bun-cache and no node_modules.

A process that starts without USERPROFILE on Windows gets the variable
of its parent, so "nothing names a cache directory" cannot be set up
there. That case is skipped on Windows. The other cases use a home
directory that cannot be made.
@robobun

robobun commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

This PR needs one decision before I change it further. @Jarred-Sumner, the question is yours.

Is a visible .bun-cache in the working directory acceptable as the fallback cache of auto-install?

A review of the diff kept the bug and the layer, and it questioned the shape. The fix itself is +19/-4 in ensure_cache_directory. The other 13 source files and the 3 docs pages exist only because the cache leaves node_modules, the one directory that every tool skips. The report that started this offered two other shapes: the temp directory, or a resolver that ignores a node_modules with only bun's cache in it.

What I checked on a debug build of this branch:

  • Two runs load the package. Then bun pm cache makes <cwd>/node_modules/.bun-cache, and the third run fails again with error: Cannot find package 'no-deps'.
  • BUN_INSTALL_CACHE_DIR=node_modules/.bun-cache is replaced by <cwd>/.bun-cache. The directory that the user names is not used.
  • The branch conflicts with main. bake: move the code into folders by job (no behavior change) #45489 moved the two bake files that it edits.
  • After one run, git status shows .bun-cache/. Prettier and ESLint enter it.

If yes, I do this here:

  1. Rebase over bake: move the code into folders by job (no behavior change) #45489. One check in the router scan replaces the four default lists.
  2. Use .bun-cache only where bun would make node_modules itself. A node_modules that exists keeps the cache, so -i does not change.
  3. Stop bun pm cache and the other commands that install nothing from making node_modules.
  4. Write a .gitignore with * into .bun-cache, so git and npm pack leave it alone.
  5. Give the name one constant, and keep only the skip lists that a user of the fallback can reach.
  6. Select the fallback where nothing names a directory, not by its spelling.

If no, I rework this PR to the resolver rule: a node_modules that holds no package (only entries such as .bun-cache or .cache) does not turn auto-install off. The rule goes where the resolver sets the flag, so the package.json version pins follow it. The skip lists and the docs lines leave the PR. This shape also repairs directories that an older bun left, and the bun pm cache case. Its cost: bun still creates node_modules in that case, and the rule is not prototyped.

The PR stays a draft until then. The body lists what is open.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants