cd /news/developer-tools/bun-v1-3-12 Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-9948] src=bun.com β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Bun v1.3.12

Bun version 1.3.12 introduces native headless browser automation via `Bun.WebView`, supporting both WebKit and Chrome backends with OS-level event dispatching. The update also adds the ability to render Markdown files directly in the terminal, an in-process `Bun.cron()` scheduler for recurring tasks, and improved async stack traces for native APIs.

read21 min views20 publishedApr 9, 2026

To install Bun

curl -fsSL https://bun.sh/install | bash
npm install -g bun
powershell -c "irm bun.sh/install.ps1|iex"
scoop install bun
brew tap oven-sh/bun
brew install bun
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun

bun upgrade

Bun.WebView

β€” Headless Browser Automation

Bun.WebView

β€” Headless Browser AutomationBun 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)

Bun.WebView programmatically controls a headless web browser in Bun[pic.twitter.com/Yp8UiNoeoy][March 18, 2026]

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. Page state properties

Bun.WebView

extends EventTarget

β€” on the Chrome backend, CDP events are dispatched as MessageEvent

s 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

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)

bun ./hello.md & Bun.markdown.ansi(string) pretty-prints markdown to terminal-friendly ansi text[pic.twitter.com/yp7jYKZL8k][April 4, 2026]

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("![alt](./logo.png)", {
  kittyGraphics: true,
});

Async stack traces for native errors

In the next version of Bun

β€” Jarred Sumner (@jarredsumner)

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][March 30, 2026]

In-process Bun.cron()

scheduler

Bun.cron()

schedulerBun.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)

Bun.cron() accepts a callback for recurring in-process tasks[pic.twitter.com/HQ3s3rxGEO][April 1, 2026]

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 returnedPromise

) 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β€” synchronous throws emitsetTimeout

uncaughtException

, rejected promises emitunhandledRejection

. Without a listener the process exits with code 1; with one, the job reschedules itself.β€” 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.--hot

safeβ€”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

)

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.isArray

intrinsicArray.isArray()

is now a JIT intrinsic, making it significantly faster in hot paths.Fasterβ€” Uses an optimized single-character search fast path.String#includes

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 newPerformPromiseThen

DFG/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#sort

when the comparator accesses.buffer

Array#includes(undefined, fromIndex)

hole handlingArray#flat

with depth 0 and derived array bailoutArray.prototype.concat

now checks all indexed accessorsArray.prototype.copyWithin

return valueSet

methods properly throw on non-object iteratornext()

resultRegExp#@@matchAll

lastIndex clampingString#replace

surrogate-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 myapp
chmod 111 myapp
./myapp  # works now on Linux

Thanks to @dylan-conway for the contribution!

URLPattern

is up to 2.3x faster

URLPattern

is up to 2.3x fasterURLPattern.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

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 instripANSI

β€” replacesStringBuilder

with a rawVector<Char>

+memcpy

, eliminating per-append bookkeeping and the final shrink-copy.UTF-16β€” long OSC payloads in UTF-16 strings (hyperlinks with emoji) now use bulk SIMD scans instead of per-codepoint stepping.stringWidth

escape state machine refactorC1 ST ( in the Zig0x9C

) recognized as OSC terminatorstringWidth

path, conforming to ECMA-48 and matching the C++consumeANSI

behavior.

stripANSI

benchmarks

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

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

bun build

on low-core machinesFixed 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()

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

availableParallelism

/ hardwareConcurrency

on LinuxIn the next version of Bun

β€” Jarred Sumner (@jarredsumner)

Threadpool & JIT threads now respect cgroup CPU limits instead of physical cores. This improves resource utilization in Docker & k8s[https://t.co/HCPy7jRCyG][April 3, 2026]

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

TCP_DEFER_ACCEPT

for Bun.serve()

on LinuxBun.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.env

being completely empty when the current working directory is inside a directory without read permission (e.g.,chmod 111

). Previously, OS-inherited environment variables passed viaexecve

were lost because the env returned early onEACCES

before reading process environment variables. (@alii) - Fixed: Memory leak where every vm.Script

,vm.SourceTextModule

, andvm.compileFunction

call 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.pipe

crashing the process when piping an object-modeReadable

into a byte-modeTransform

/Writable

. TheERR_INVALID_ARG_TYPE

error is now properly emitted on the destination stream'serror

event instead of being thrown as an uncatchable exception, matching Node.js behavior. - Fixed: node:dns/promises.getDefaultResultOrder

beingundefined

anddns.getDefaultResultOrder()

returning the function object instead of a string ("ipv4first"

/"ipv6first"

/"verbatim"

). Also added the missinggetServers

export todns.promises

. This broke Vite 8 builds under Bun. (@dylan-conway) - Fixed: fs.realpathSync("/")

throwingENOENT

when running Bun under FreeBSD's Linuxulator compatibility layer or in minimal containers without/proc

mounted (@ant-kurt) - Fixed: fs.statSync().ino

returningINT64_MAX

(9223372036854775807

) for files with inodes β‰₯ 2⁢³, causing all files on NFS mounts with high 64-bit inodes to report the same inode number.dev

andrdev

were 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.captureStackTrace

now includes async stack frames (e.g.at async <fn>

) matching the behavior ofnew Error().stack

(@Jarred-Sumner) - Fixed: a rare crash in Error.captureStackTrace

on error objects whose.stack

had already been accessed (@Jarred-Sumner) - Fixed: assert.partialDeepStrictEqual

crashing when comparing arrays - Fixed: fs.stat

,fs.lstat

, andfs.fstat

throwingEPERM

on Linux when running under seccomp filters that block thestatx

syscall (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 withoutnew

scrambled 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 Stats

incorrectly returnedfalse

because stat instances used a different prototype object thanStats.prototype

. Methods like.isFile()

still worked, but identity checks andinstanceof

did 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 concurrentasync

handlers that resumed after anawait

couldn'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.SQL

MySQL adapter returning empty results for SELECT queries against MySQL-compatible databases (StarRocks, TiDB, SingleStore, etc.) that don't support theCLIENT_DEPRECATE_EOF

capability. 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:sql

MySQL 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.parse

where the logger's internal message list was not freed on error paths - Fixed: Bun.listen()

andBun.connect()

could crash with certain invalidhostname

orunix

values. Now throws aTypeError

instead. - Fixed: a crash accessing server.url

with 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.setServers

with certain invalid inputs - Fixed: Bun.dns.lookup()

could crash with certain invalid inputs - Fixed: Glob

scanner 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 anENAMETOOLONG

error instead. (@dylan-conway) - Fixed: Unix socket paths longer than 104 bytes (the sun_path

limit) now work correctly on macOS. Previously,Bun.serve({ unix })

andfetch({ unix })

would fail withENAMETOOLONG

when the socket path exceeded this limit. (@Jarred-Sumner) - Fixed: a crash when reading .fd

on a TLS listener created withBun.listen({ tls })

  • Fixed: crashes in Bun.FFI.linkSymbols()

andBun.FFI.viewSource

with invalid symbol descriptors β€” now throw aTypeError

instead - 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 MessagePort

being queued indefinitely and never delivered. 5000 Γ— 64KBpostMessage

calls to a closed port dropped RSS from 332MB to 1.5MB. (@sosukesuzuki) - Fixed: AbortController.signal.reason

silently becomingundefined

after garbage collection when only the controller was retained. (@sosukesuzuki) - Fixed: use-after-free race in BroadcastChannel

when a worker-owned channel was destroyed while another thread looked it up. (@sosukesuzuki) - Fixed: CookieMap.toJSON()

could crash with numeric cookie names - Fixed: String.raw

corrupting null bytes (U+0000) in tagged template literals, emitting the 6-character string\uFFFD

instead of preserving the original byte. This affected libraries likewasm-audio-decoders

that embed WASM binaries as yEncoded strings in template literals. - Fixed: AbortSignal

memory leak whenReadableStream.prototype.pipeTo

is called with asignal

option and the pipe never completes. A reference cycle betweenAbortSignal

and its abort algorithm callbacks prevented garbage collection even after all user-side references were dropped. (@sosukesuzuki) - Fixed: crash when calling bytes()

orarrayBuffer()

on aResponse

whose body was created from an async iterable (Symbol.asyncIterator

) - Fixed: crash when calling ReadableStream.blob()

after theResponse

body was already consumed, now properly rejects withERR_BODY_ALREADY_USED

  • Fixed: a crash in Request.formData()

/Response.formData()

/Blob.formData()

when theContent-Type

header contained a malformed boundary value (@dylan-conway) - Fixed: HTTP server now correctly rejects requests with conflicting duplicate Content-Length

headers 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.toPrimitive

throws - Fixed: a crash that could occur when a stack overflow happened during error message formatting

JavaScript bundler

  • Fixed: bun build --compile

on 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_INTERP

is now normalized back to the standard FHS path so compiled binaries are portable across Linux systems. (@Jarred-Sumner) - Fixed a crash in bun build --compile

when 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.extend

with certain invalid inputs - Fixed: --elide-lines

flag 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)

── more in #developer-tools 4 stories Β· sorted by recency
── more on @bun 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/bun-v1-3-12] indexed:0 read:21min 2026-04-09 Β· β€”