To install Bun
curl -fsSL https://bun.sh/install | bashnpm install -g bunpowershell -c "irm bun.sh/install.ps1|iex"scoop install bunbrew tap oven-sh/bunbrew install bundocker pull oven/bundocker run --rm --init --ulimit memlock=-1:-1 oven/bunTo upgrade Bun
bun upgradeBun.WebView β Headless Browser Automation
Bun now ships with native headless browser automation built into the runtime. Two backends, one API:
- WebKit (macOS default) β uses the system WKWebView. Zero external dependencies.
- Chrome (cross-platform) β Chrome/Chromium via DevTools Protocol. Auto-detects installed browsers or accepts a custom path.
In the next version of Bun
β Jarred Sumner (@jarredsumner) March 18, 2026
Bun.WebView programmatically controls a headless web browser in Bun pic.twitter.com/Yp8UiNoeoy
All input is dispatched as OS-level events β sites can't distinguish view.click() from a real mouse click (isTrusted: true). Selector-based methods auto-wait for actionability, Playwright-style: the element must be attached, visible, stable, and unobscured before the action fires.
await using view = new Bun.WebView({ width: 800, height: 600 });
await view.navigate("https://bun.sh");
await view.click("a[href='/docs']"); // waits for actionability, native click
await view.scroll(0, 400); // native wheel event, isTrusted: true
await view.scrollTo("#install"); // scrolls every ancestor, waits for visible
const title = await view.evaluate("document.title");
const png = await view.screenshot({ format: "jpeg", quality: 90 });
await Bun.write("page.jpg", png);
All methods work across both backends:
| Method | Description |
|---|---|
navigate(url) | Navigate to a URL |
evaluate(expr) | Evaluate JavaScript in the page |
screenshot({format, quality, encoding}) | Capture a PNG/JPEG/WebP screenshot |
click(x, y) / click(selector) | Click at coordinates or a CSS selector |
type(text) | Type text into the focused element |
press(key, {modifiers}) | Press a key with optional modifiers |
scroll(dx, dy) / scrollTo(selector) | Scroll by delta or to an element |
goBack() / goForward() / reload() | Navigation controls |
resize(w, h) | Resize the viewport |
cdp(method, params) | Raw Chrome DevTools Protocol call |
view.url / view.title / view.loading | Page state properties |
Bun.WebView extends EventTarget β on the Chrome backend, CDP events are dispatched as MessageEvents with the params on event.data. Constructor options include backend ("webkit", "chrome", or { type: "chrome", path, argv }), console to capture page logs, and dataStore for persistent profiles. One browser subprocess is shared per Bun process; additional new Bun.WebView() calls open tabs in the same instance.
Render Markdown in the Terminal with bun ./file.md
You can now render Markdown files directly in your terminal. When you run bun ./file.md, Bun reads the file, renders it as beautifully formatted ANSI output, and prints it to stdout with no JavaScript VM startup overhead.
In the next version of Bun
β Bun (@bunjavascript) April 4, 2026
bun ./hello.md & Bun.markdown.ansi(string) pretty-prints markdown to terminal-friendly ansi text pic.twitter.com/yp7jYKZL8k
You can also use the new Bun.markdown.ansi() API programmatically:
// Render markdown to an ANSI-colored string
const out = Bun.markdown.ansi("# Hello\n\n**bold** and *italic*\n");
process.stdout.write(out);
// Plain text mode β no escape codes
const plain = Bun.markdown.ansi("# Hello", { colors: false });
// Enable clickable hyperlinks
const linked = Bun.markdown.ansi("[docs](https://bun.sh)", {
hyperlinks: true,
});
// Custom line width for wrapping
const wrapped = Bun.markdown.ansi(longText, { columns: 60 });
// Inline images via Kitty Graphics Protocol (Kitty, WezTerm, Ghostty)
const withImg = Bun.markdown.ansi("", {
kittyGraphics: true,
});
Async stack traces for native errors
In the next version of Bun
β Jarred Sumner (@jarredsumner) March 30, 2026
Async stacktraces are supported on native APIs like node:fs, Bun.write, node:http, node:dns & more.
This makes debugging easier pic.twitter.com/PHospWtxtg
In-process Bun.cron() scheduler
Bun.cron now supports an in-process callback overload that runs a function on a cron schedule. This is ideal for long-running servers and containers where you want scheduled work that shares state with the rest of your application.
In the next version of Bun
β alistair (@alistaiir) April 1, 2026
Bun.cron() accepts a callback for recurring in-process tasks pic.twitter.com/HQ3s3rxGEO
This complements the existing OS-level Bun.cron(path, schedule, title) which registers persistent crontab/launchd/Task Scheduler entries. The in-process variant is lighter, works identically across platforms, and lets your handler access database pools, caches, and module-level state directly.
Key behaviors:
- No overlap β the next fire is scheduled only after the handler (and any returned
Promise) settles. Slow async work won't pile up concurrent runs. - Scheduled in UTC β
0 9 * * *means 9:00 UTC, regardless of the system time zone. (The OS-levelBun.cron(path, schedule, title)variant uses system local time, since that's how crontab/launchd/Task Scheduler work.) - Error handling matches
setTimeoutβ synchronous throws emituncaughtException, rejected promises emitunhandledRejection. Without a listener the process exits with code 1; with one, the job reschedules itself. --hotsafe β all in-process cron jobs are cleared before the module graph re-evaluates, so editing the schedule, handler, or removing the call entirely all take effect on save without leaking timers.Disposableβusing job = Bun.cron(...)auto-stops at scope exit.ref/unrefβ.ref()(default) keeps the process alive;.unref()lets it exit naturally.
// Error handling example
process.on("unhandledRejection", (err) => console.error("cron failed:", err));
Bun.cron("* * * * *", async () => {
await mightThrow(); // logged, then retried next minute
});
Thanks to @alii for the contribution!
UDP Socket: ICMP Error Handling and Truncation Detection
Two improvements to Bun.udpSocket() that bring it closer to libuv/Node.js behavior:
ICMP errors no longer silently close the socket. On Linux, sending a UDP packet to an unreachable port previously caused the socket to silently close, breaking all other sends on the same socket. Now, ICMP errors (port unreachable, host unreachable, TTL exceeded, etc.) are surfaced through the error handler, and the socket stays open:
const sock = await Bun.udpSocket({
socket: {
error(err) {
console.log(err.code); // 'ECONNREFUSED'
},
},
});
sock.send("ping", 1, "127.0.0.1"); // dead port β error handler fires, socket stays open
Truncated datagrams are now detectable. When a received datagram is larger than the receive buffer, the kernel silently truncates it. The data callback now receives a fifth flags argument so you can tell truncated payloads from complete ones:
const sock = await Bun.udpSocket({
socket: {
data(socket, data, port, address, flags) {
if (flags.truncated) {
console.log("Datagram was truncated!");
}
},
},
});
Unix Domain Socket Lifecycle Now Matches Node.js
Bun's unix domain socket behavior was inverted from Node.js/libuv in two important ways:
| Node.js / libuv | Bun (before) | |
|---|---|---|
| Existing file at bind time | EADDRINUSE | Silently unlinks and binds anyway |
Socket file after close() | Removed | Left on disk |
This meant Bun could silently steal a live socket from another process on listen(), and leaked .sock files on stop()/close().
Now Bun matches Node.js semantics: binding to an existing socket file correctly returns EADDRINUSE, and closing a listener automatically cleans up the socket file. This applies to Bun.listen, Bun.serve, and net.Server with unix sockets.
import { existsSync } from "node:fs";
const listener = Bun.listen({
unix: "/tmp/my.sock",
socket: { data() {}, open() {} },
});
existsSync("/tmp/my.sock"); // true
listener.stop();
existsSync("/tmp/my.sock"); // false β automatically cleaned up
// Binding to an existing socket now correctly throws EADDRINUSE
// instead of silently taking over the socket
import { listen } from "bun";
const a = listen({ unix: "/tmp/my.sock", socket: { data() {}, open() {} } });
try {
// Previously: silently unlinked and stole the socket
// Now: throws EADDRINUSE, matching Node.js behavior
const b = listen({ unix: "/tmp/my.sock", socket: { data() {}, open() {} } });
} catch (e) {
console.log(e.code); // "EADDRINUSE"
} finally {
a.stop();
}
Upgraded JavaScriptCore Engine
Bun's underlying JavaScript engine (WebKit's JavaScriptCore) has been upgraded with over 1,650 upstream commits, bringing significant performance improvements, new language features, and bug fixes.
Explicit Resource Management (using and await using)
The using and await using declarations from the TC39 Explicit Resource Management proposal are now supported natively in JavaScriptCore:
function readFile(path) {
using file = openFile(path); // file[Symbol.dispose]() called automatically at end of block
return file.read();
}
async function fetchData(url) {
await using connection = await connect(url); // connection[Symbol.asyncDispose]() awaited at end of block
return connection.getData();
}
JIT Compiler Improvements
- Quick tier-up β Functions that are proven stable now tier up to optimized DFG/FTL compilation faster, improving steady-state performance.
Array.isArrayintrinsic βArray.isArray()is now a JIT intrinsic, making it significantly faster in hot paths.- Faster
String#includesβ Uses an optimized single-character search fast path. - Improved BigInt performance β Smaller memory footprint and faster arithmetic operations.
- Better register allocation β Rewritten greedy register allocator coalescing for improved generated code.
- Faster promise resolution β Micro-optimized promise reaction triggering and microtask queue draining, with a new
PerformPromiseThenDFG/FTL optimization node.
WebAssembly Improvements
- SIMD shuffle optimizations and additional ARM64/x64 SIMD instruction support.
- Memory64 bounds checking fixes.
- Improved BBQ and OMG compiler codegen (conditional selects, better write barriers, tail-call fixes).
JavaScript Spec Conformance Fixes
TypedArray#sortwhen the comparator accesses.bufferArray#includes(undefined, fromIndex)hole handlingArray#flatwith depth 0 and derived array bailoutArray.prototype.concatnow checks all indexed accessorsArray.prototype.copyWithinreturn valueSetmethods properly throw on non-object iteratornext()resultRegExp#@@matchAlllastIndex clampingString#replacesurrogate-advancement fix for non-unicode regexps- Named vs numbered backreference handling in RegExp
- Private fields no longer have attributes set when sealing/freezing objects
Memory Allocator (libpas) Improvements
- Retag-on-scavenge for improved memory safety
- Page-based zeroing threshold reduced from 64MB to 1MB for faster large allocations
- Faster bitfit heap utilization
Thanks to @sosukesuzuki for the contribution!
Improved standalone executables on Linux
Standalone executables created with bun build --compile on Linux now use a proper ELF section (.bun) to embed the module graph, matching the existing approach on macOS and Windows. Previously, the embedded data was read from /proc/self/exe at startup, which failed when the binary had execute-only permissions (chmod 111).
With this change, the kernel maps the data via PT_LOAD during execve β meaning zero file I/O at startup and no read permission required on the binary.
bun build --compile app.ts --outfile myappchmod 111 myapp./myapp # works now on LinuxThanks to @dylan-conway for the contribution!
URLPattern is up to 2.3x faster
URLPattern.test() and URLPattern.exec() are now significantly faster. The internal regex matching now calls the compiled regex engine directly instead of allocating temporary JavaScript objects for each URL component, eliminating up to 24 GC allocations per call.
| Benchmark | Before | After | Speedup |
|---|---|---|---|
test() match - named groups | 1.05 Β΅s | 487 ns | 2.16x |
test() no-match | 579 ns | 337 ns | 1.72x |
test() match - simple | 971 ns | 426 ns | 2.28x |
test() match - string pattern | 946 ns | 434 ns | 2.18x |
exec() match - named groups | 1.97 Β΅s | 1.38 Β΅s | 1.43x |
exec() no-match | 583 ns | 336 ns | 1.73x |
exec() match - simple | 1.89 Β΅s | 1.30 Β΅s | 1.45x |
const pattern = new URLPattern({ pathname: "/api/users/:id/posts/:postId" });
// 2.16x faster
pattern.test("https://example.com/api/users/42/posts/123");
// 1.43x faster
pattern.exec("https://example.com/api/users/42/posts/123");
As a side effect, URLPattern internals no longer pollute RegExp.lastMatch / RegExp.$N β previously, calling pattern.test(url) would leak internal regex state into these legacy static properties.
Thanks to @sosukesuzuki for the contribution!
Faster Bun.stripANSI and Bun.stringWidth
SIMD optimizations across Bun.stripANSI, Bun.stringWidth, and the shared ANSI parsing helpers used by Bun.sliceAnsi, Bun.wrapAnsi, and Node's readline.getStringWidth.
Key improvements:
- 4Γ-unrolled SIMD prologue for escape character scanning β processes 64 bytes at a time instead of 16, reducing the cost of the NEONβGPR transfer that gates the loop branch.
- SIMD terminator scans inside ANSI escape parsing β CSI and OSC payloads (like hyperlink URLs) are now skipped in bulk instead of byte-by-byte.
- Lazy flat buffer allocation in
stripANSIβ replacesStringBuilderwith a rawVector<Char>+memcpy, eliminating per-append bookkeeping and the final shrink-copy. - UTF-16
stringWidthescape state machine refactor β long OSC payloads in UTF-16 strings (hyperlinks with emoji) now use bulk SIMD scans instead of per-codepoint stepping. - C1 ST (
0x9C) recognized as OSC terminator in the ZigstringWidthpath, conforming to ECMA-48 and matching the C++consumeANSIbehavior.
stripANSI benchmarks
| Input | Before | After | Improvement |
|---|---|---|---|
| Plain ASCII (1000 chars, no escapes) | 65.40 ns | 16.88 ns | ~4Γ |
| OSC 8 hyperlink (45 chars) | 59.57 ns | 45.12 ns | 24% faster |
| Bash 150KB | 78.97 Β΅s | 71.56 Β΅s | 9% faster |
stringWidth benchmarks
| Input | Before | After | Improvement |
|---|---|---|---|
| Hyperlink + emoji, UTF-16 (440K chars) | ~2.0 ms | 180 Β΅s | ~11Γ |
| Truecolor SGR (140K chars) | ~135 Β΅s | 120 Β΅s | 10% faster |
| Hyperlink, Latin-1 (445K chars) | ~135 Β΅s | 119 Β΅s | 11% faster |
Compared to npm string-width, Bun is 4β822Γ faster depending on input size and content, and correctly handles all three OSC terminator variants (BEL, ESC \, and C1 ST) where the npm package only recognizes BEL.
Faster bun build on low-core machines
Fixed a thread-pool bug that left the bundler running with one fewer worker thread than intended. Most impactful on low-core machines where one thread is a larger share of the pool:
| Cores | Before | After | Speedup |
|---|---|---|---|
| 2 | 554β561 ms | 375β392 ms | 1.43β1.47Γ |
| 4 | 321 ms | 301 ms | ~1.07Γ |
| 16 | 303β316 ms | 292β296 ms | ~1.02β1.08Γ |
Benchmark: bun build on an 11,669-module project (three.js + @mui/material + @mui/icons-material, 10.45 MB output).
Faster Bun.Glob.scan()
Bun.Glob.scan() no longer opens and reads the same directory twice for patterns with a **/X/... boundary (e.g. **/node_modules/**/*.js). Gains scale with how much of the tree sits under the boundary β up to 2x on deeply nested trees. Patterns without a boundary (e.g. **/*.ts) are unchanged.
const glob = new Bun.Glob("**/node_modules/**/*.js");
// This is now up to 2x faster
for await (const path of glob.scan({ cwd: "./my-project" })) {
// ...
}
Additionally, Bun.Glob on Windows now pushes wildcard filters down to the kernel via NtQueryDirectoryFile, so non-matching entries are discarded before reaching userspace β up to 2.4Γ faster for simple patterns like *.js or pkg-*/lib/*.js in directories with a low match ratio. Patterns using **, ?, [...], or {...} bypass the filter and behave as before.
Cgroup-aware availableParallelism / hardwareConcurrency on Linux
In the next version of Bun
β Jarred Sumner (@jarredsumner) April 3, 2026
Threadpool & JIT threads now respect cgroup CPU limits instead of physical cores. This improves resource utilization in Docker & k8shttps://t.co/HCPy7jRCyG
Keep-Alive for HTTPS Proxy CONNECT Tunnels
Bun now reuses CONNECT tunnels for HTTPS-through-proxy requests. Previously, every proxied HTTPS request performed a fresh CONNECT handshake and TLS negotiation. Now, the tunnel and inner TLS session are pooled and reused across sequential requests to the same target through the same proxy with the same credentials β matching the behavior of Node.js + undici.
This dramatically reduces latency and connection overhead when making multiple HTTPS requests through a proxy:
// All three requests now reuse a single CONNECT tunnel
// instead of establishing 3 separate tunnels + TLS handshakes
for (let i = 0; i < 3; i++) {
const res = await fetch("https://example.com/api", {
proxy: "http://user:pass@proxy.example.com:8080",
});
console.log(res.status);
}
Tunnels are keyed by proxy host/port, proxy credentials, target host/port, and TLS configuration β so different targets or different credentials correctly use separate tunnels.
This also fixes intermittent Malformed_HTTP_Response errors that some users encountered when using fetch with HTTPS proxies.
Thanks to @cirospaciari for the contribution!
TCP_DEFER_ACCEPT for Bun.serve() on Linux
Bun.serve() now sets TCP_DEFER_ACCEPT on Linux (and SO_ACCEPTFILTER "dataready" on FreeBSD), the same optimization nginx uses to reduce latency for incoming HTTP connections.
Previously, accepting a new connection required two event loop wake-ups β one for the accept and another to discover the socket was readable. With TCP_DEFER_ACCEPT, the kernel defers the accept until the client has actually sent data (the HTTP request or TLS ClientHello), collapsing the two wake-ups into one:
Before:
- epoll wake β accept new socket
- Return to epoll
- epoll wake β socket readable β
recv()β process β respond
After:
- epoll wake β accept new socket (data already buffered) β
recv()β process β respond
This is especially impactful for short-lived connections (e.g. HTTP/1.1 with Connection: close). Bun.listen() and net.createServer() are unchanged, since they may serve protocols where the server sends first. No effect on macOS or Windows.
Bugfixes
Node.js compatibility improvements
- Fixed:
process.envbeing completely empty when the current working directory is inside a directory without read permission (e.g.,chmod 111). Previously, OS-inherited environment variables passed viaexecvewere lost because the env loader returned early onEACCESbefore reading process environment variables. (@alii) - Fixed: Memory leak where every
vm.Script,vm.SourceTextModule, andvm.compileFunctioncall leaked the resulting object due to a reference cycle in the internalNodeVMScriptFetcher(@sosukesuzuki) - Fixed:
pipeline(Readable.fromWeb(res.body), createWriteStream(...))permanently stalling (and eventually spinning at 100% CPU) when pipingfetch()response bodies under concurrency, caused by a race between the HTTP thread's body callback and JS accessingres.body(@dylan-conway) - Fixed:
Readable.prototype.pipecrashing the process when piping an object-modeReadableinto a byte-modeTransform/Writable. TheERR_INVALID_ARG_TYPEerror is now properly emitted on the destination stream'serrorevent instead of being thrown as an uncatchable exception, matching Node.js behavior. - Fixed:
node:dns/promises.getDefaultResultOrderbeingundefinedanddns.getDefaultResultOrder()returning the function object instead of a string ("ipv4first"/"ipv6first"/"verbatim"). Also added the missinggetServersexport todns.promises. This broke Vite 8 builds under Bun. (@dylan-conway) - Fixed:
fs.realpathSync("/")throwingENOENTwhen running Bun under FreeBSD's Linuxulator compatibility layer or in minimal containers without/procmounted (@ant-kurt) - Fixed:
fs.statSync().inoreturningINT64_MAX(9223372036854775807) for files with inodes β₯ 2βΆΒ³, causing all files on NFS mounts with high 64-bit inodes to report the same inode number.devandrdevwere also affected. All stat fields now match Node.js behavior for bothStats(Number) andBigIntStats(BigInt) paths. (@dylan-conway) - Fixed:
process.stdout.end(callback)firing the callback before all data was flushed, causing output truncation at power-of-2 boundaries (64KB, 128KB, etc.) when the callback calledprocess.exit(0) - Fixed:
Error.captureStackTracenow includes async stack frames (e.g.at async <fn>) matching the behavior ofnew Error().stack(@Jarred-Sumner) - Fixed: a rare crash in
Error.captureStackTraceon error objects whose.stackhad already been accessed (@Jarred-Sumner) - Fixed:
assert.partialDeepStrictEqualcrashing when comparing arrays - Fixed:
fs.stat,fs.lstat, andfs.fstatthrowingEPERMon Linux when running under seccomp filters that block thestatxsyscall (e.g., older Docker versions < 18.04, libseccomp < 2.3.3, and various CI sandboxes). Bun now matches libuv's fallback behavior by also handlingEPERM,EINVAL, and abnormal positive return codes fromstatx. - Fixed:
fs.Stats(...)called withoutnewscrambled property values β 8 of 10 integer fields (e.g.ino,size,mode) were assigned to the wrong property names due to a slot-order mismatch in the internal constructor path. (@dylan-conway) - Fixed:
statSync(path) instanceof Statsincorrectly returnedfalsebecause stat instances used a different prototype object thanStats.prototype. Methods like.isFile()still worked, but identity checks andinstanceofdid not match Node.js behavior. (@dylan-conway) - Updated built-in root TLS certificates to NSS 3.121, the version shipping in Firefox 149. Adds e-Szigno TLS Root CA 2023 and corrects the label for OISTE Server Root RSA G1. (@cirospaciari)
Bun APIs
- Fixed: setting
process.env.HTTP_PROXY,HTTPS_PROXY, orNO_PROXY(and lowercase variants) at runtime had no effect on subsequentfetch()calls because proxy config was only read once at startup. Changes now take effect on the nextfetch(). (@cirospaciari) - Fixed: the event loop processing at most 1,024 ready I/O events per tick when more were pending, adding latency on servers handling thousands of concurrent connections. Bun now drains the full backlog in a single tick, matching libuv's
uv__io_poll. (@Jarred-Sumner) - Fixed: a
Bun.serve()performance cliff where concurrentasynchandlers that resumed after anawaitcouldn't batch their writes (cork buffer contention), dropping throughput from ~190k req/s to ~22k req/s. Also fixes a potential use-after-free where closed sockets could remain referenced in the drain loop. (@Jarred-Sumner) - Fixed: a lost-wakeup race in Bun's internal thread pool that could cause
fs.promises,Bun.file().text(),Bun.write(),crypto.subtle, and the package manager to hang indefinitely on aarch64 (Apple Silicon, ARM Linux). x86_64 was not affected. (@dylan-conway) - Fixed: Memory leak in
Bun.serve()when aPromise<Response>from the fetch handler never settles after the client disconnects (@Jarred-Sumner) - Fixed:
Bun.SQLMySQL adapter returning empty results for SELECT queries against MySQL-compatible databases (StarRocks, TiDB, SingleStore, etc.) that don't support theCLIENT_DEPRECATE_EOFcapability. Bun now properly negotiates capabilities with the server per the MySQL protocol spec and correctly handles legacy EOF packets. - Fixed: per-query memory leaks in the
bun:sqlMySQL adapter that caused RSS to grow unboundedly until OOM on Linux. Three native allocation leaks were fixed: column name allocations not freed on cleanup or when overwritten during prepared statement reuse, and parameter slice allocations not freed after query execution. - Fixed: memory leak in
Bun.TOML.parsewhere the logger's internal message list was not freed on error paths - Fixed:
Bun.listen()andBun.connect()could crash with certain invalidhostnameorunixvalues. Now throws aTypeErrorinstead. - Fixed: a crash accessing
server.urlwith an invalid unix socket path - Fixed: DNS cache entries that were stale but still referenced by in-flight connections would never expire, causing stale DNS results to persist indefinitely (@dylan-conway)
- Fixed: potential crash in
Bun.dns.setServerswith certain invalid inputs - Fixed:
Bun.dns.lookup()could crash with certain invalid inputs - Fixed:
Globscanner crashing or looping infinitely when scanning deeply nested directory trees or self-referential symlinks where the accumulated path exceeds the OS path length limit. Now properly returns anENAMETOOLONGerror instead. (@dylan-conway) - Fixed: Unix socket paths longer than 104 bytes (the
sun_pathlimit) now work correctly on macOS. Previously,Bun.serve({ unix })andfetch({ unix })would fail withENAMETOOLONGwhen the socket path exceeded this limit. (@Jarred-Sumner) - Fixed: a crash when reading
.fdon a TLS listener created withBun.listen({ tls }) - Fixed: crashes in
Bun.FFI.linkSymbols()andBun.FFI.viewSourcewith invalid symbol descriptors β now throw aTypeErrorinstead - Fixed: edge case crash when passing an out-of-range value as a file descriptor to APIs like
S3Client.write
Web APIs
- Fixed: unbounded memory growth from messages sent to a closed
MessagePortbeing queued indefinitely and never delivered. 5000 Γ 64KBpostMessagecalls to a closed port dropped RSS from 332MB to 1.5MB. (@sosukesuzuki) - Fixed:
AbortController.signal.reasonsilently becomingundefinedafter garbage collection when only the controller was retained. (@sosukesuzuki) - Fixed: use-after-free race in
BroadcastChannelwhen a worker-owned channel was destroyed while another thread looked it up. (@sosukesuzuki) - Fixed:
CookieMap.toJSON()could crash with numeric cookie names - Fixed:
String.rawcorrupting null bytes (U+0000) in tagged template literals, emitting the 6-character string\uFFFDinstead of preserving the original byte. This affected libraries likewasm-audio-decodersthat embed WASM binaries as yEncoded strings in template literals. - Fixed:
AbortSignalmemory leak whenReadableStream.prototype.pipeTois called with asignaloption and the pipe never completes. A reference cycle betweenAbortSignaland its abort algorithm callbacks prevented garbage collection even after all user-side references were dropped. (@sosukesuzuki) - Fixed: crash when calling
bytes()orarrayBuffer()on aResponsewhose body was created from an async iterable (Symbol.asyncIterator) - Fixed: crash when calling
ReadableStream.blob()after theResponsebody was already consumed, now properly rejects withERR_BODY_ALREADY_USED - Fixed: a crash in
Request.formData()/Response.formData()/Blob.formData()when theContent-Typeheader contained a malformed boundary value (@dylan-conway) - Fixed: HTTP server now correctly rejects requests with conflicting duplicate
Content-Lengthheaders per RFC 9112, preventing potential request smuggling attacks (@dylan-conway) - Fixed: WebSocket connections crashing when headers, URLs, or proxy config contained non-ASCII characters. The upgrade request now correctly decodes all inputs as UTF-8. (@Jarred-Sumner)
- Fixed: edge case crash formatting error messages when
Symbol.toPrimitivethrows - Fixed: a crash that could occur when a stack overflow happened during error message formatting
JavaScript bundler
- Fixed:
bun build --compileon NixOS and Guix producing executables that only ran on the exact same Nix generation, because the compiled binary inherited a/nix/store/...ELF interpreter path.PT_INTERPis now normalized back to the standard FHS path so compiled binaries are portable across Linux systems. (@Jarred-Sumner) - Fixed a crash in
bun build --compilewhen CSS files are passed as entry points alongside JS/TS entry points
bun test
- Fixed:
mock.module()could crash when the first argument is not a string - Fixed: a crash that could occur when
mock.module()triggered auto-install during module resolution - Fixed: potential crash in
expect.extendwith certain invalid inputs - Fixed:
--elide-linesflag no longer exits with an error in non-terminal environments (e.g., CI, Git hooks). The flag is now silently ignored when stdout is not a TTY, allowing the same command to work in both interactive and non-interactive contexts. (@alii)
Bun Shell
- Fixed:
Bun.$.braces()could crash when called with an empty string
Windows
- Fixed: tar archive extraction on Windows could write files outside the extraction directory when an entry contained an absolute path (e.g.
C:\...or UNC paths) β these entries are now skipped (@dylan-conway)