Skip to content

resolver: park the cached directories below a busted directory until it is read again - #46960

Closed
robobun wants to merge 4 commits into
mainfrom
robobun/5f7683a2/bust-dir-cache-descendants
Closed

robobun wants to merge 4 commits into
mainfrom
robobun/5f7683a2/bust-dir-cache-descendants

Conversation

@robobun

@robobun robobun commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • After Resolver::bust_dir_cache drops a directory, the cached directories below it keep the old package.json and tsconfig.json. Under bun --hot, an edit of tsconfig.json paths reaches a root file and not a file in src/.
  • dir_info_uncached (src/resolver/resolver.rs) copies the parent's pointers and slot index into each child. The bust removed one key.

Fix

  • A bust also takes the directories below it out of the index. The next lookup reads the busted directory again and compares it with its old slot (DirInfo::is_same_after_reread).
  • Same: the slot and the directories below go back with no I/O. Different: the directories below are read again.
  • Unchanged package.json bytes and tsconfig.json hashes keep the old parse, so an unchanged directory compares equal.
  • Verified: six test files, each fails with main's src/ and passes here (Notes). Self-review: this is the wrong layer. Closed, see the closing comment.

Background

  • DirInfo is the resolver's cached record of one directory. A failed require(), a --hot directory event and the dev server bust it.
  • A filled slot is never written again, except entries, now atomic.
  • Weighed a drop of everything below on every bust: it parses each package.json again per --hot save. Weighed a generation stamp: it adds instructions to every cache hit.

Downsides

  • A bust with a full cache costs more: +12% / +62% / +84% instructions at 20 / 200 / 1000 packages.
  • +10,240 B text.
  • Edits that bust nothing stay stale (Notes).
Notes

Who busts. bun --hot and --watch on each directory event, the dev server (watched directories, a deleted file, a failed relative import), the runtime after a failed require() or import(), Bun.build for an entry point that does not exist, and FileSystemRouter.reload(). So the bug needs no watcher: require("/app/not-there.js") drops /app.

What a directory takes from the one above it. The nearest package.json (for imports, "type", sideEffects, the browser map, auto-install), the nearest tsconfig.json (paths, baseUrl, jsx), its real path, and the index of its parent's slot, which the node_modules walk reads later. All of them were stale below a busted directory.

Tests. Each file fails on a debug build with src/ from main and passes with this branch.

  • test/cli/hot/hot.test.ts: tsconfig.json paths and package.json imports under bun --hot.
  • test/bundler/resolver/cache-runtime.test.ts: in one process, with a failed require() as the bust. A changed, created and deleted package.json, a changed tsconfig.json, a new node_modules folder, a changed file that a tsconfig.json below extends, two busts in both orders, and a bust that changes nothing.
  • test/bundler/resolver/cache-invalidation.test.ts: Bun.build, imports and "type".
  • test/bake/dev/bundle.test.ts: the dev server, after a file next to the package.json is deleted.
  • test/js/bun/util/filesystem_router.test.ts: reload() and a directory that has no route.
  • test/js/bun/resolve/tsconfig-extends-leak.test.ts (debug builds): 20 busts with no change parse tsconfig.json once, open the root again each time, and open no directory below it again.

Also run on the debug build, all pass: test/js/bun/resolve/resolve.test.ts, test/bundler/resolver/cache-node-compat.test.ts, all of test/cli/hot/hot.test.ts, test/cli/watch/watch.test.ts, test/bake/dev/bundle.test.ts, test/bake/dev/hot.test.ts, test/js/bun/util/filesystem_router.test.ts. Tests ran on Linux x64 only. bun run rust:check-all compiles the change for all 12 targets. The Windows and kqueue watcher paths were not run.

Measurements. Release builds of main 179664e and this branch, Linux x64.

Cache hit, unchanged:

  • Resolver::dir_info_cached_maybe_log: 770 B and 200 instructions in both builds. 0 instructions differ once addresses are masked.
  • BSSMapInner::get_or_put, both instantiations: 1,208 B and 324 instructions in both. 0 differ.
  • A run that only resolves cached paths: -0.87% / +0.03% / -0.02% instructions at 20 / 200 / 1000 packages.

One bun --hot save of a root-level file with no config change, 42 cached directories, counted with breakpoints over 10 saves, 3 runs:

per save main this PR
directory fills 1 1 (the root, none below it)
package.json parses kept for the life of the process 1 0
tsconfig.json parses 1 0
entries to the cold lookup path 1 42 (one for each cached directory, no fill)

One failed require() from the project root with every directory cached. Instructions for the whole process, bust run minus no-bust run, per bust. The number includes the failed require() itself and the re-read of the root.

packages main this PR
20 65,119 72,795 (+12%)
200 202,341 327,147 (+62%)
1000 794,868 1,462,145 (+84%)

The added work is the scan of the index, and one trip through the cold path for each directory on its next lookup. Wall clock did not resolve it: over 5 interleaved rounds the difference between main and this PR was inside the spread between two runs of main.

2000 failed require() calls in a loop from the project root. Here the directories below the root stay out of the index, so each later scan is short:

packages main, us each this PR, us each main, KB kept each this PR, KB kept each
20 14 to 27 12 to 14 12.5 to 12.8 8.6 to 8.8
200 25 to 29 13 to 14 22.0 to 23.0 6.5 to 7.7
1000 74 to 108 13 to 16 61.1 to 62.0 6.6 to 7.7

Two runs of main differ by about 15%, and by more on the first run after the fixture is written. The remaining 6.5 to 8.8 KB was not investigated.

Size: text 107,582,646 to 107,592,886 B. size_of::<DirInfo>() stays 104 B (const assert). bust_dir_cache 262 to 3,373 B, dir_info_cached_miss 6,465 to 7,589 B, dir_info_uncached 17,800 to 18,871 B, new fill_dir_info 2,652 B.

Not measured: syscalls per save, the cost of one real config edit, the syscalls that the check of extended tsconfig.json files adds, and the peak size of the table of directories that a bust took out.

Still stale after this PR.

  • An edit that busts nothing. The dev server does not watch package.json or tsconfig.json. kqueue reports a directory only when an entry is replaced, so an in-place write on macOS raises no bust.
  • A directory cached under a symlink path when the bust names the real path.
  • A tsconfig.json that extends a file outside every busted directory, when that directory is not below the busted one.
  • A path cached as not found below the busted directory.

Question for a maintainer. DirInfo.entries is now an atomic that is stored once more when an unchanged directory is read again. Every other field of a slot is still written once. Without it, an unchanged directory needs a new slot on every bust, and the directories below it would hold the index of an orphan.

Related. #41534 has the same index scan in another form. #36928 keeps the same parse for unchanged bytes, for every parse. #46911 adds one more field that a directory takes from its parent: is_same_after_reread names every field, so it does not compile until that field has an answer.

@robobun

robobun commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:20 AM PT - Oct 26th, 2026

❌ @autofix-ci[bot], your commit f90e283 has 1 failures in Build #132974 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 46960

That installs a local version of the PR into your bun-46960 executable, so you can run:

bun-46960 --bun

@robobun

robobun commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: closed. The self-review found that this fix is at the wrong layer. The closing comment has the reason and the evidence.

How I reproduced the problem on main (179664e, Linux x64):

  1. Make a project with tsconfig.json (paths: @lib to ./a.ts), root.ts and src/sub.ts that both import @lib, and main.ts that imports both and prints what each one got.
  2. Run bun --hot main.ts. It prints a for both.
  3. Replace tsconfig.json so that @lib points to ./b.ts, then save main.ts.
  4. main prints b for root.ts and a for src/sub.ts. This PR prints b for both.

The same happens with no watcher: in one process, require() of a file that does not exist drops its directory from the resolver's cache, and a file in a subdirectory keeps the old package.json imports.

Comment thread src/bun_alloc/lib.rs
Comment on lines +2316 to +2318
/// [`remove`](Self::remove) that also returns the hash of the key and what the key mapped
/// to: a slot, `NOT_FOUND` or `UNASSIGNED`. A slot stays allocated, so
/// [`publish`](Self::publish) can map the hash to it again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/bun_alloc/lib.rs
Comment on lines +2325 to +2326
/// Unmaps every key whose slot satisfies `pred` and passes its hash and slot to `unpublished`.
/// A key that maps to `NOT_FOUND` or `UNASSIGNED` has no slot and stays.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/bun_alloc/lib.rs
Comment on lines +2358 to +2359
/// Maps `hash` to a slot that [`unpublish`](Self::unpublish) or
/// [`unpublish_where`](Self::unpublish_where) returned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +28 to +29
// the resolver mutex. The one field that changes later is `entries`, which is
// atomic: see `ListingIndex`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +155 to +157
/// `DirInfo::entries`: where the listing of the directory is in `RealFS::entries`. A bust drops
/// the listing. A re-read that finds the directory as it was keeps the slot, because its
/// subdirectories hold the index of the slot, and stores the index of the new listing here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +219 to +223
/// Whether a re-read of a busted directory (`self`) left it as `before`, the slot it had when
/// it was busted. A subdirectory copies the pointers and `InsideNodeModules` when it is
/// filled, and reads the rest through `parent` and `enclosing_browser_scope` later. So when
/// this is true the directory keeps `before`, and what was filled below it still holds. Every
/// field is named here so that a new one does not compile until it has an answer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +459 to +460
/// A slot that a bust took out of the index. A lookup of its key settles it: see
/// `Resolver::dir_info_cached_miss`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +464 to +465
/// True: this directory was busted. It is read again and compared with the slot.
/// False: a directory above it was. The slot is good while its parent has the slot it had.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +475 to +476
/// By the hash of the key, as in the index. Every access holds `RealFS::entries_mutex`, which
/// also keeps a fill from writing the slots that are read here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +499 to +503
/// Takes the directory `key` out of the index, with every directory below it: each one holds
/// what it took from the directory above it when it was filled. Returns whether `key` was in
/// the index, as `BSSMapInner::remove` does.
///
/// The caller holds `RealFS::entries_mutex`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +533 to +536
/// The slot that a bust took out of the index for the key with this hash, if one did. The
/// caller publishes the slot again or leaves it out for good.
///
/// The caller holds `RealFS::entries_mutex`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +544 to +547
/// Leaves every parked directory below `dir` out of the index for good: `dir` is not what it
/// was when they were filled.
///
/// The caller holds `RealFS::entries_mutex`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +563 to +564
/// `SourceFile::hash` of the file when the configurations that extend it read it. `None`:
/// two of them read different bytes, so one of them is not what the file has now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +574 to +577
/// By the hash of the key of the directory that has the file. A re-read of a busted directory
/// leaves its slot as it is when its own configuration did not change. A change to one of these
/// files is what tells it that a directory below it is still not to be published as it was.
/// Every access holds `RealFS::entries_mutex`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/dir_info.rs
Comment on lines +591 to +592
/// Records what a tsconfig.json that was just loaded read from `path`, a file it extends.
/// `dir_hash` is the key hash of the directory of `path`, and `hash` is `SourceFile::hash`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/lib.rs
Comment on lines +1199 to +1204
///
/// The caller holds `entries_mutex`: `entries` is the process-global
/// BSSMap singleton, and the callers of `Resolver::bust_dir_cache`
/// (transpiler / hot-reloader / VM) reach it without `RESOLVER_MUTEX`.
/// `bust_dir_cache` evicts the directory from its own cache under the
/// same lock.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment on lines +97 to +98
/// Whether this parse read the dependency groups (`IncludeDependencies::Main` or `Local`).
/// A parse that did not cannot stand in for one that has to.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment on lines +410 to +412
/// The path and the bytes of the package.json of the directory `input_path`. Apart from
/// [`parse_contents`](Self::parse_contents) so that a re-read of the directory can compare the
/// bytes with those of the parse it already has.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +2479 to +2482
///
/// The cached directories below the path leave the index too: each one holds what it took
/// from the directory above it. The next lookup of one puts it back as it is, unless a
/// directory between it and the path, or the path, is different when it is read again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +3452 to +3453
// A slot that this function filled has no parent. One that `dir_info_cached`
// filled is not what this function fills: it stays out of the index.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +3457 to +3458
// A bust of a directory above this one took it out of the index, and it took
// nothing from that directory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +4100 to +4101
// `use_shared_buffer = false` above, so `entry_contents` is
// `Contents::Owned`/`Empty`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +4131 to +4133
// `TSConfigJSON` owns `Box<[u8]>` copies of every field, so the source
// bytes are dead once `parse` returns and can be dropped with the
// local `Source`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +4179 to +4182
/// `before_bust`: the parse that the directory had when it was busted. The re-read keeps it
/// when the file has the same bytes, so that the directory compares equal to its slot
/// (`DirInfo::is_same_after_reread`) and the parse is not made and kept a second time. The
/// options of the resolver that made it stay, as they do for a directory that is not busted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +4553 to +4554
// A bust of a directory above this one took it out of the index. What is
// above it now has the slots it had then, so this one is as it was filled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +4850 to +4851
// `put`; resolver mutex held. The pointer is rooted at the singleton,
// so the map reborrows inside `fill_dir_info` cannot pop it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +6232 to +6238
/// Fills a slot for a directory that is not in the index, and puts it in the index.
///
/// `before_bust` is the slot that the directory had when `bust_dir_cache` took it out of the
/// index. The directories below it hold the index of that slot and what they took from it.
/// So when the directory is as that slot has it, that slot goes back with the new listing,
/// and they are still right. When it is not, it gets a new slot, and those that the bust
/// took out with it stay out: the next lookup of one reads it again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +6315 to +6316
/// `before_bust`: the slot that the directory had when it was busted. Its package.json and
/// tsconfig.json parses are kept when the files are as they were.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +6706 to +6707
// It is for the resolvers that read it. What is wrong with it is not an error of
// one that reads another in its place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +6885 to +6886
/// Whether each file that `config` was read from is as it was then, and the first one is
/// `path`: a re-read of the directory then keeps `config`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/resolver/resolver.rs
Comment on lines +6902 to +6904
/// Whether a file in the directory with this key hash, which the tsconfig.json of another
/// directory extends, is not what that one read. The directories below this one that a bust
/// parked are not published again then.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment on lines +167 to +169
/// The files that `Resolver::load_tsconfig` read for this configuration: the file of the
/// directory, then each file that the one before it extends. A re-read of the directory
/// keeps the configuration while each file hashes to the same value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

@robobun

robobun commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Closing this PR. The self-review found that the fix is at the wrong layer, and my checks of its main points agree (Linux x64).

The bug is real. This prints a.js a.js, then b.js a.js on 1.3.13, 1.4.2 and main. Node prints a.js a.js twice.

const fs = require("fs"), os = require("os"), path = require("path");
const dir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "stale-"));
const write = (file, text) => {
  fs.mkdirSync(path.dirname(path.join(dir, file)), { recursive: true });
  fs.writeFileSync(path.join(dir, file), text);
};
const imports = target => JSON.stringify({ name: "app", imports: { "#lib": target } });
const probe = `module.exports = () => require("path").basename(require.resolve("#lib"));`;
write("package.json", imports("./a.js"));
write("a.js", "");
write("b.js", "");
write("probe.js", probe);
write("src/probe.js", probe);
const seen = () => ["", "src"].map(sub => require(path.join(dir, sub, "probe.js"))()).join(" ");
console.log(seen());
write("package.json", imports("./b.js"));
try {
  require(path.join(dir, "not-there.js")); // a require that fails drops the cached directory
} catch {}
console.log(seen());

Why this PR is the wrong fix

  • It does not make a bust arrive. Put the config in the project root and the entry in src/, as the bun init React templates do. Nothing busts the root, so main and this PR both keep the old target under bun --hot.
  • Every bust pays for it. With 500 route directories and 5,000 cached directories, FileSystemRouter.reload() takes 42 to 45 ms here. It takes 11 to 12 ms on main.
  • Resolver: after a miss, check on disk the cached facts that the lookup read, and retry if one is stale #40279 removes the busts after a failed require() and after a missing Bun.build entry point. 11 of the 15 new test cases here use them.
  • docs/guides/http/hot.mdx says to restart Bun after a change to tsconfig.json.

Recommended instead

A maintainer decides first

@robobun robobun closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant