{"slug": "bun-v1-3-12", "title": "Bun v1.3.12", "summary": "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.", "body_md": "#### To install Bun\n\n```\ncurl -fsSL https://bun.sh/install | bash\nnpm install -g bun\npowershell -c \"irm bun.sh/install.ps1|iex\"\nscoop install bun\nbrew tap oven-sh/bun\nbrew install bun\ndocker pull oven/bun\ndocker run --rm --init --ulimit memlock=-1:-1 oven/bun\n```\n\n#### To upgrade Bun\n\n```\nbun upgrade\n```\n\n`Bun.WebView`\n\n— Headless Browser Automation\n\n`Bun.WebView`\n\n— Headless Browser AutomationBun now ships with native headless browser automation built into the runtime. Two backends, one API:\n\n**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.\n\nIn the next version of Bun\n\n— Jarred Sumner (@jarredsumner)\n\nBun.WebView programmatically controls a headless web browser in Bun[pic.twitter.com/Yp8UiNoeoy][March 18, 2026]\n\nAll input is dispatched as OS-level events — sites can't distinguish `view.click()`\n\nfrom a real mouse click (`isTrusted: true`\n\n). Selector-based methods auto-wait for actionability, Playwright-style: the element must be attached, visible, stable, and unobscured before the action fires.\n\n```\nawait using view = new Bun.WebView({ width: 800, height: 600 });\nawait view.navigate(\"https://bun.sh\");\n\nawait view.click(\"a[href='/docs']\"); // waits for actionability, native click\nawait view.scroll(0, 400); // native wheel event, isTrusted: true\nawait view.scrollTo(\"#install\"); // scrolls every ancestor, waits for visible\n\nconst title = await view.evaluate(\"document.title\");\nconst png = await view.screenshot({ format: \"jpeg\", quality: 90 });\nawait Bun.write(\"page.jpg\", png);\n```\n\nAll methods work across both backends:\n\n| Method | Description |\n|---|---|\n`navigate(url)` | Navigate to a URL |\n`evaluate(expr)` | Evaluate JavaScript in the page |\n`screenshot({format, quality, encoding})` | Capture a PNG/JPEG/WebP screenshot |\n`click(x, y)` / `click(selector)` | Click at coordinates or a CSS selector |\n`type(text)` | Type text into the focused element |\n`press(key, {modifiers})` | Press a key with optional modifiers |\n`scroll(dx, dy)` / `scrollTo(selector)` | Scroll by delta or to an element |\n`goBack()` / `goForward()` / `reload()` | Navigation controls |\n`resize(w, h)` | Resize the viewport |\n`cdp(method, params)` | Raw Chrome DevTools Protocol call |\n`view.url` / `view.title` / `view.loading` | Page state properties |\n\n`Bun.WebView`\n\nextends `EventTarget`\n\n— on the Chrome backend, CDP events are dispatched as `MessageEvent`\n\ns with the params on `event.data`\n\n. Constructor options include `backend`\n\n(`\"webkit\"`\n\n, `\"chrome\"`\n\n, or `{ type: \"chrome\", path, argv }`\n\n), `console`\n\nto capture page logs, and `dataStore`\n\nfor persistent profiles. One browser subprocess is shared per Bun process; additional `new Bun.WebView()`\n\ncalls open tabs in the same instance.\n\n[Render Markdown in the Terminal with ](#render-markdown-in-the-terminal-with-bun-file-md)`bun ./file.md`\n\n`bun ./file.md`\n\nYou can now render Markdown files directly in your terminal. When you run `bun ./file.md`\n\n, Bun reads the file, renders it as beautifully formatted ANSI output, and prints it to stdout with no JavaScript VM startup overhead.\n\nIn the next version of Bun\n\n— Bun (@bunjavascript)\n\nbun ./hello.md & Bun.markdown.ansi(string) pretty-prints markdown to terminal-friendly ansi text[pic.twitter.com/yp7jYKZL8k][April 4, 2026]\n\nYou can also use the new `Bun.markdown.ansi()`\n\nAPI programmatically:\n\n``` js\n// Render markdown to an ANSI-colored string\nconst out = Bun.markdown.ansi(\"# Hello\\n\\n**bold** and *italic*\\n\");\nprocess.stdout.write(out);\n\n// Plain text mode — no escape codes\nconst plain = Bun.markdown.ansi(\"# Hello\", { colors: false });\n\n// Enable clickable hyperlinks\nconst linked = Bun.markdown.ansi(\"[docs](https://bun.sh)\", {\n  hyperlinks: true,\n});\n\n// Custom line width for wrapping\nconst wrapped = Bun.markdown.ansi(longText, { columns: 60 });\n\n// Inline images via Kitty Graphics Protocol (Kitty, WezTerm, Ghostty)\nconst withImg = Bun.markdown.ansi(\"![alt](./logo.png)\", {\n  kittyGraphics: true,\n});\n```\n\n[Async stack traces for native errors](#async-stack-traces-for-native-errors)\n\nIn the next version of Bun\n\n— Jarred Sumner (@jarredsumner)\n\nAsync stacktraces are supported on native APIs like node:fs, Bun.write, node:http, node:dns & more.\n\nThis makes debugging easier[pic.twitter.com/PHospWtxtg][March 30, 2026]\n\n[In-process ](#in-process-bun-cron-scheduler)`Bun.cron()`\n\nscheduler\n\n`Bun.cron()`\n\nscheduler`Bun.cron`\n\nnow 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.\n\nIn the next version of Bun\n\n— alistair (@alistaiir)\n\nBun.cron() accepts a callback for recurring in-process tasks[pic.twitter.com/HQ3s3rxGEO][April 1, 2026]\n\nThis complements the existing OS-level `Bun.cron(path, schedule, title)`\n\nwhich 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.\n\n**Key behaviors:**\n\n**No overlap**— the next fire is scheduled only after the handler (and any returned`Promise`\n\n) settles. Slow async work won't pile up concurrent runs.**Scheduled in UTC**—`0 9 * * *`\n\nmeans 9:00 UTC, regardless of the system time zone. (The OS-level`Bun.cron(path, schedule, title)`\n\nvariant uses system local time, since that's how crontab/launchd/Task Scheduler work.)**Error handling matches**— synchronous throws emit`setTimeout`\n\n`uncaughtException`\n\n, rejected promises emit`unhandledRejection`\n\n. 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`\n\nsafe—`Disposable`\n\n`using job = Bun.cron(...)`\n\nauto-stops at scope exit.—`ref`\n\n/`unref`\n\n`.ref()`\n\n(default) keeps the process alive;`.unref()`\n\nlets it exit naturally.\n\n``` js\n// Error handling example\nprocess.on(\"unhandledRejection\", (err) => console.error(\"cron failed:\", err));\n\nBun.cron(\"* * * * *\", async () => {\n  await mightThrow(); // logged, then retried next minute\n});\n```\n\nThanks to @alii for the contribution!\n\n[UDP Socket: ICMP Error Handling and Truncation Detection](#udp-socket-icmp-error-handling-and-truncation-detection)\n\nTwo improvements to `Bun.udpSocket()`\n\nthat bring it closer to libuv/Node.js behavior:\n\n**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`\n\nhandler, and the socket stays open:\n\n``` js\nconst sock = await Bun.udpSocket({\n  socket: {\n    error(err) {\n      console.log(err.code); // 'ECONNREFUSED'\n    },\n  },\n});\nsock.send(\"ping\", 1, \"127.0.0.1\"); // dead port — error handler fires, socket stays open\n```\n\n**Truncated datagrams are now detectable.** When a received datagram is larger than the receive buffer, the kernel silently truncates it. The `data`\n\ncallback now receives a fifth `flags`\n\nargument so you can tell truncated payloads from complete ones:\n\n``` js\nconst sock = await Bun.udpSocket({\n  socket: {\n    data(socket, data, port, address, flags) {\n      if (flags.truncated) {\n        console.log(\"Datagram was truncated!\");\n      }\n    },\n  },\n});\n```\n\n[Unix Domain Socket Lifecycle Now Matches Node.js](#unix-domain-socket-lifecycle-now-matches-node-js)\n\nBun's unix domain socket behavior was inverted from Node.js/libuv in two important ways:\n\n| Node.js / libuv | Bun (before) | |\n|---|---|---|\n| Existing file at bind time | `EADDRINUSE` | Silently unlinks and binds anyway |\nSocket file after `close()` | Removed | Left on disk |\n\nThis meant Bun could silently steal a live socket from another process on `listen()`\n\n, and leaked `.sock`\n\nfiles on `stop()`\n\n/`close()`\n\n.\n\nNow Bun matches Node.js semantics: binding to an existing socket file correctly returns `EADDRINUSE`\n\n, and closing a listener automatically cleans up the socket file. This applies to `Bun.listen`\n\n, `Bun.serve`\n\n, and `net.Server`\n\nwith unix sockets.\n\n``` js\nimport { existsSync } from \"node:fs\";\n\nconst listener = Bun.listen({\n  unix: \"/tmp/my.sock\",\n  socket: { data() {}, open() {} },\n});\n\nexistsSync(\"/tmp/my.sock\"); // true\n\nlistener.stop();\n\nexistsSync(\"/tmp/my.sock\"); // false — automatically cleaned up\n// Binding to an existing socket now correctly throws EADDRINUSE\n// instead of silently taking over the socket\nimport { listen } from \"bun\";\n\nconst a = listen({ unix: \"/tmp/my.sock\", socket: { data() {}, open() {} } });\n\ntry {\n  // Previously: silently unlinked and stole the socket\n  // Now: throws EADDRINUSE, matching Node.js behavior\n  const b = listen({ unix: \"/tmp/my.sock\", socket: { data() {}, open() {} } });\n} catch (e) {\n  console.log(e.code); // \"EADDRINUSE\"\n} finally {\n  a.stop();\n}\n```\n\n[Upgraded JavaScriptCore Engine](#upgraded-javascriptcore-engine)\n\nBun'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.\n\n[Explicit Resource Management (](#explicit-resource-management-using-and-await-using)`using`\n\nand `await using`\n\n)\n\n`using`\n\nand `await using`\n\n)The `using`\n\nand `await using`\n\ndeclarations from the [TC39 Explicit Resource Management proposal](https://github.com/tc39/proposal-explicit-resource-management) are now supported natively in JavaScriptCore:\n\n```\nfunction readFile(path) {\n  using file = openFile(path); // file[Symbol.dispose]() called automatically at end of block\n  return file.read();\n}\n\nasync function fetchData(url) {\n  await using connection = await connect(url); // connection[Symbol.asyncDispose]() awaited at end of block\n  return connection.getData();\n}\n```\n\n[JIT Compiler Improvements](#jit-compiler-improvements)\n\n**Quick tier-up**— Functions that are proven stable now tier up to optimized DFG/FTL compilation faster, improving steady-state performance.—`Array.isArray`\n\nintrinsic`Array.isArray()`\n\nis now a JIT intrinsic, making it significantly faster in hot paths.**Faster**— Uses an optimized single-character search fast path.`String#includes`\n\n**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`PerformPromiseThen`\n\nDFG/FTL optimization node.\n\n[WebAssembly Improvements](#webassembly-improvements)\n\n- SIMD shuffle optimizations and additional ARM64/x64 SIMD instruction support.\n- Memory64 bounds checking fixes.\n- Improved BBQ and OMG compiler codegen (conditional selects, better write barriers, tail-call fixes).\n\n[JavaScript Spec Conformance Fixes](#javascript-spec-conformance-fixes)\n\n`TypedArray#sort`\n\nwhen the comparator accesses`.buffer`\n\n`Array#includes(undefined, fromIndex)`\n\nhole handling`Array#flat`\n\nwith depth 0 and derived array bailout`Array.prototype.concat`\n\nnow checks all indexed accessors`Array.prototype.copyWithin`\n\nreturn value`Set`\n\nmethods properly throw on non-object iterator`next()`\n\nresult`RegExp#@@matchAll`\n\nlastIndex clamping`String#replace`\n\nsurrogate-advancement fix for non-unicode regexps- Named vs numbered backreference handling in RegExp\n- Private fields no longer have attributes set when sealing/freezing objects\n\n[Memory Allocator (libpas) Improvements](#memory-allocator-libpas-improvements)\n\n- Retag-on-scavenge for improved memory safety\n- Page-based zeroing threshold reduced from 64MB to 1MB for faster large allocations\n- Faster bitfit heap utilization\n\nThanks to @sosukesuzuki for the contribution!\n\n[Improved standalone executables on Linux](#improved-standalone-executables-on-linux)\n\nStandalone executables created with `bun build --compile`\n\non Linux now use a proper ELF section (`.bun`\n\n) to embed the module graph, matching the existing approach on macOS and Windows. Previously, the embedded data was read from `/proc/self/exe`\n\nat startup, which failed when the binary had execute-only permissions (`chmod 111`\n\n).\n\nWith this change, the kernel maps the data via `PT_LOAD`\n\nduring `execve`\n\n— meaning zero file I/O at startup and no read permission required on the binary.\n\n```\nbun build --compile app.ts --outfile myapp\nchmod 111 myapp\n./myapp  # works now on Linux\n```\n\nThanks to @dylan-conway for the contribution!\n\n`URLPattern`\n\nis up to 2.3x faster\n\n`URLPattern`\n\nis up to 2.3x faster`URLPattern.test()`\n\nand `URLPattern.exec()`\n\nare 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.\n\n| Benchmark | Before | After | Speedup |\n|---|---|---|---|\n`test()` match - named groups | 1.05 µs | 487 ns | 2.16x |\n`test()` no-match | 579 ns | 337 ns | 1.72x |\n`test()` match - simple | 971 ns | 426 ns | 2.28x |\n`test()` match - string pattern | 946 ns | 434 ns | 2.18x |\n`exec()` match - named groups | 1.97 µs | 1.38 µs | 1.43x |\n`exec()` no-match | 583 ns | 336 ns | 1.73x |\n`exec()` match - simple | 1.89 µs | 1.30 µs | 1.45x |\n\n``` js\nconst pattern = new URLPattern({ pathname: \"/api/users/:id/posts/:postId\" });\n\n// 2.16x faster\npattern.test(\"https://example.com/api/users/42/posts/123\");\n\n// 1.43x faster\npattern.exec(\"https://example.com/api/users/42/posts/123\");\n```\n\nAs a side effect, `URLPattern`\n\ninternals no longer pollute `RegExp.lastMatch`\n\n/ `RegExp.$N`\n\n— previously, calling `pattern.test(url)`\n\nwould leak internal regex state into these legacy static properties.\n\nThanks to @sosukesuzuki for the contribution!\n\n[Faster ](#faster-bun-stripansi-and-bun-stringwidth)`Bun.stripANSI`\n\nand `Bun.stringWidth`\n\n`Bun.stripANSI`\n\nand `Bun.stringWidth`\n\nSIMD optimizations across `Bun.stripANSI`\n\n, `Bun.stringWidth`\n\n, and the shared ANSI parsing helpers used by `Bun.sliceAnsi`\n\n, `Bun.wrapAnsi`\n\n, and Node's `readline.getStringWidth`\n\n.\n\n**Key improvements:**\n\n**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`\n\n— replaces`StringBuilder`\n\nwith a raw`Vector<Char>`\n\n+`memcpy`\n\n, 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`\n\nescape state machine refactor**C1 ST (** in the Zig`0x9C`\n\n) recognized as OSC terminator`stringWidth`\n\npath, conforming to ECMA-48 and matching the C++`consumeANSI`\n\nbehavior.\n\n`stripANSI`\n\nbenchmarks\n\n`stripANSI`\n\nbenchmarks| Input | Before | After | Improvement |\n|---|---|---|---|\n| Plain ASCII (1000 chars, no escapes) | 65.40 ns | 16.88 ns | ~4× |\n| OSC 8 hyperlink (45 chars) | 59.57 ns | 45.12 ns | 24% faster |\n| Bash 150KB | 78.97 µs | 71.56 µs | 9% faster |\n\n`stringWidth`\n\nbenchmarks\n\n`stringWidth`\n\nbenchmarks| Input | Before | After | Improvement |\n|---|---|---|---|\n| Hyperlink + emoji, UTF-16 (440K chars) | ~2.0 ms | 180 µs | ~11× |\n| Truecolor SGR (140K chars) | ~135 µs | 120 µs | 10% faster |\n| Hyperlink, Latin-1 (445K chars) | ~135 µs | 119 µs | 11% faster |\n\nCompared to npm `string-width`\n\n, Bun is **4–822× faster** depending on input size and content, and correctly handles all three OSC terminator variants (BEL, ESC `\\`\n\n, and C1 ST) where the npm package only recognizes BEL.\n\n[Faster ](#faster-bun-build-on-low-core-machines)`bun build`\n\non low-core machines\n\n`bun build`\n\non 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:\n\n| Cores | Before | After | Speedup |\n|---|---|---|---|\n| 2 | 554–561 ms | 375–392 ms | 1.43–1.47× |\n| 4 | 321 ms | 301 ms | ~1.07× |\n| 16 | 303–316 ms | 292–296 ms | ~1.02–1.08× |\n\n*Benchmark: bun build on an 11,669-module project (three.js + @mui/material + @mui/icons-material, 10.45 MB output).*\n\n[Faster ](#faster-bun-glob-scan)`Bun.Glob.scan()`\n\n`Bun.Glob.scan()`\n\n`Bun.Glob.scan()`\n\nno longer opens and reads the same directory twice for patterns with a `**/X/...`\n\nboundary (e.g. `**/node_modules/**/*.js`\n\n). 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`\n\n) are unchanged.\n\n``` js\nconst glob = new Bun.Glob(\"**/node_modules/**/*.js\");\n\n// This is now up to 2x faster\nfor await (const path of glob.scan({ cwd: \"./my-project\" })) {\n  // ...\n}\n```\n\nAdditionally, `Bun.Glob`\n\non Windows now pushes wildcard filters down to the kernel via `NtQueryDirectoryFile`\n\n, so non-matching entries are discarded before reaching userspace — up to **2.4×** faster for simple patterns like `*.js`\n\nor `pkg-*/lib/*.js`\n\nin directories with a low match ratio. Patterns using `**`\n\n, `?`\n\n, `[...]`\n\n, or `{...}`\n\nbypass the filter and behave as before.\n\n[Cgroup-aware ](#cgroup-aware-availableparallelism-hardwareconcurrency-on-linux)`availableParallelism`\n\n/ `hardwareConcurrency`\n\non Linux\n\n`availableParallelism`\n\n/ `hardwareConcurrency`\n\non LinuxIn the next version of Bun\n\n— Jarred Sumner (@jarredsumner)\n\nThreadpool & 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]\n\n[Keep-Alive for HTTPS Proxy CONNECT Tunnels](#keep-alive-for-https-proxy-connect-tunnels)\n\nBun 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.\n\nThis dramatically reduces latency and connection overhead when making multiple HTTPS requests through a proxy:\n\n```\n// All three requests now reuse a single CONNECT tunnel\n// instead of establishing 3 separate tunnels + TLS handshakes\nfor (let i = 0; i < 3; i++) {\n  const res = await fetch(\"https://example.com/api\", {\n    proxy: \"http://user:pass@proxy.example.com:8080\",\n  });\n  console.log(res.status);\n}\n```\n\nTunnels are keyed by proxy host/port, proxy credentials, target host/port, and TLS configuration — so different targets or different credentials correctly use separate tunnels.\n\nThis also fixes intermittent `Malformed_HTTP_Response`\n\nerrors that some users encountered when using `fetch`\n\nwith HTTPS proxies.\n\nThanks to @cirospaciari for the contribution!\n\n`TCP_DEFER_ACCEPT`\n\nfor `Bun.serve()`\n\non Linux\n\n`TCP_DEFER_ACCEPT`\n\nfor `Bun.serve()`\n\non Linux`Bun.serve()`\n\nnow sets `TCP_DEFER_ACCEPT`\n\non Linux (and `SO_ACCEPTFILTER \"dataready\"`\n\non FreeBSD), the same optimization nginx uses to reduce latency for incoming HTTP connections.\n\nPreviously, 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`\n\n, 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:\n\n**Before:**\n\n- epoll wake → accept new socket\n- Return to epoll\n- epoll wake → socket readable →\n`recv()`\n\n→ process → respond\n\n**After:**\n\n- epoll wake → accept new socket (data already buffered) →\n`recv()`\n\n→ process → respond\n\nThis is especially impactful for short-lived connections (e.g. HTTP/1.1 with `Connection: close`\n\n). `Bun.listen()`\n\nand `net.createServer()`\n\nare unchanged, since they may serve protocols where the server sends first. No effect on macOS or Windows.\n\n[Bugfixes](#bugfixes)\n\n[Node.js compatibility improvements](#node-js-compatibility-improvements)\n\n- Fixed:\n`process.env`\n\nbeing completely empty when the current working directory is inside a directory without read permission (e.g.,`chmod 111`\n\n). Previously, OS-inherited environment variables passed via`execve`\n\nwere lost because the env loader returned early on`EACCES`\n\nbefore reading process environment variables. (@alii) - Fixed: Memory leak where every\n`vm.Script`\n\n,`vm.SourceTextModule`\n\n, and`vm.compileFunction`\n\ncall leaked the resulting object due to a reference cycle in the internal`NodeVMScriptFetcher`\n\n(@sosukesuzuki) - Fixed:\n`pipeline(Readable.fromWeb(res.body), createWriteStream(...))`\n\npermanently stalling (and eventually spinning at 100% CPU) when piping`fetch()`\n\nresponse bodies under concurrency, caused by a race between the HTTP thread's body callback and JS accessing`res.body`\n\n(@dylan-conway) - Fixed:\n`Readable.prototype.pipe`\n\ncrashing the process when piping an object-mode`Readable`\n\ninto a byte-mode`Transform`\n\n/`Writable`\n\n. The`ERR_INVALID_ARG_TYPE`\n\nerror is now properly emitted on the destination stream's`error`\n\nevent instead of being thrown as an uncatchable exception, matching Node.js behavior. - Fixed:\n`node:dns/promises.getDefaultResultOrder`\n\nbeing`undefined`\n\nand`dns.getDefaultResultOrder()`\n\nreturning the function object instead of a string (`\"ipv4first\"`\n\n/`\"ipv6first\"`\n\n/`\"verbatim\"`\n\n). Also added the missing`getServers`\n\nexport to`dns.promises`\n\n. This broke Vite 8 builds under Bun. (@dylan-conway) - Fixed:\n`fs.realpathSync(\"/\")`\n\nthrowing`ENOENT`\n\nwhen running Bun under FreeBSD's Linuxulator compatibility layer or in minimal containers without`/proc`\n\nmounted (@ant-kurt) - Fixed:\n`fs.statSync().ino`\n\nreturning`INT64_MAX`\n\n(`9223372036854775807`\n\n) for files with inodes ≥ 2⁶³, causing all files on NFS mounts with high 64-bit inodes to report the same inode number.`dev`\n\nand`rdev`\n\nwere also affected. All stat fields now match Node.js behavior for both`Stats`\n\n(Number) and`BigIntStats`\n\n(BigInt) paths. (@dylan-conway) - Fixed:\n`process.stdout.end(callback)`\n\nfiring the callback before all data was flushed, causing output truncation at power-of-2 boundaries (64KB, 128KB, etc.) when the callback called`process.exit(0)`\n\n- Fixed:\n`Error.captureStackTrace`\n\nnow includes async stack frames (e.g.`at async <fn>`\n\n) matching the behavior of`new Error().stack`\n\n(@Jarred-Sumner) - Fixed: a rare crash in\n`Error.captureStackTrace`\n\non error objects whose`.stack`\n\nhad already been accessed (@Jarred-Sumner) - Fixed:\n`assert.partialDeepStrictEqual`\n\ncrashing when comparing arrays - Fixed:\n`fs.stat`\n\n,`fs.lstat`\n\n, and`fs.fstat`\n\nthrowing`EPERM`\n\non Linux when running under seccomp filters that block the`statx`\n\nsyscall (e.g., older Docker versions < 18.04, libseccomp < 2.3.3, and various CI sandboxes). Bun now matches libuv's fallback behavior by also handling`EPERM`\n\n,`EINVAL`\n\n, and abnormal positive return codes from`statx`\n\n. - Fixed:\n`fs.Stats(...)`\n\ncalled without`new`\n\nscrambled property values — 8 of 10 integer fields (e.g.`ino`\n\n,`size`\n\n,`mode`\n\n) were assigned to the wrong property names due to a slot-order mismatch in the internal constructor path. (@dylan-conway) - Fixed:\n`statSync(path) instanceof Stats`\n\nincorrectly returned`false`\n\nbecause stat instances used a different prototype object than`Stats.prototype`\n\n. Methods like`.isFile()`\n\nstill worked, but identity checks and`instanceof`\n\ndid 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)\n\n[Bun APIs](#bun-apis)\n\n- Fixed: setting\n`process.env.HTTP_PROXY`\n\n,`HTTPS_PROXY`\n\n, or`NO_PROXY`\n\n(and lowercase variants) at runtime had no effect on subsequent`fetch()`\n\ncalls because proxy config was only read once at startup. Changes now take effect on the next`fetch()`\n\n. (@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\n`uv__io_poll`\n\n. (@Jarred-Sumner) - Fixed: a\n`Bun.serve()`\n\nperformance cliff where concurrent`async`\n\nhandlers that resumed after an`await`\n\ncouldn'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\n`fs.promises`\n\n,`Bun.file().text()`\n\n,`Bun.write()`\n\n,`crypto.subtle`\n\n, and the package manager to hang indefinitely on aarch64 (Apple Silicon, ARM Linux). x86_64 was not affected. (@dylan-conway) - Fixed: Memory leak in\n`Bun.serve()`\n\nwhen a`Promise<Response>`\n\nfrom the fetch handler never settles after the client disconnects (@Jarred-Sumner) - Fixed:\n`Bun.SQL`\n\nMySQL adapter returning empty results for SELECT queries against MySQL-compatible databases (StarRocks, TiDB, SingleStore, etc.) that don't support the`CLIENT_DEPRECATE_EOF`\n\ncapability. 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\n`bun:sql`\n\nMySQL 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\n`Bun.TOML.parse`\n\nwhere the logger's internal message list was not freed on error paths - Fixed:\n`Bun.listen()`\n\nand`Bun.connect()`\n\ncould crash with certain invalid`hostname`\n\nor`unix`\n\nvalues. Now throws a`TypeError`\n\ninstead. - Fixed: a crash accessing\n`server.url`\n\nwith 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)\n- Fixed: potential crash in\n`Bun.dns.setServers`\n\nwith certain invalid inputs - Fixed:\n`Bun.dns.lookup()`\n\ncould crash with certain invalid inputs - Fixed:\n`Glob`\n\nscanner 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 an`ENAMETOOLONG`\n\nerror instead. (@dylan-conway) - Fixed: Unix socket paths longer than 104 bytes (the\n`sun_path`\n\nlimit) now work correctly on macOS. Previously,`Bun.serve({ unix })`\n\nand`fetch({ unix })`\n\nwould fail with`ENAMETOOLONG`\n\nwhen the socket path exceeded this limit. (@Jarred-Sumner) - Fixed: a crash when reading\n`.fd`\n\non a TLS listener created with`Bun.listen({ tls })`\n\n- Fixed: crashes in\n`Bun.FFI.linkSymbols()`\n\nand`Bun.FFI.viewSource`\n\nwith invalid symbol descriptors — now throw a`TypeError`\n\ninstead - Fixed: edge case crash when passing an out-of-range value as a file descriptor to APIs like\n`S3Client.write`\n\n[Web APIs](#web-apis)\n\n- Fixed: unbounded memory growth from messages sent to a closed\n`MessagePort`\n\nbeing queued indefinitely and never delivered. 5000 × 64KB`postMessage`\n\ncalls to a closed port dropped RSS from 332MB to 1.5MB. (@sosukesuzuki) - Fixed:\n`AbortController.signal.reason`\n\nsilently becoming`undefined`\n\nafter garbage collection when only the controller was retained. (@sosukesuzuki) - Fixed: use-after-free race in\n`BroadcastChannel`\n\nwhen a worker-owned channel was destroyed while another thread looked it up. (@sosukesuzuki) - Fixed:\n`CookieMap.toJSON()`\n\ncould crash with numeric cookie names - Fixed:\n`String.raw`\n\ncorrupting null bytes (U+0000) in tagged template literals, emitting the 6-character string`\\uFFFD`\n\ninstead of preserving the original byte. This affected libraries like`wasm-audio-decoders`\n\nthat embed WASM binaries as yEncoded strings in template literals. - Fixed:\n`AbortSignal`\n\nmemory leak when`ReadableStream.prototype.pipeTo`\n\nis called with a`signal`\n\noption and the pipe never completes. A reference cycle between`AbortSignal`\n\nand its abort algorithm callbacks prevented garbage collection even after all user-side references were dropped. (@sosukesuzuki) - Fixed: crash when calling\n`bytes()`\n\nor`arrayBuffer()`\n\non a`Response`\n\nwhose body was created from an async iterable (`Symbol.asyncIterator`\n\n) - Fixed: crash when calling\n`ReadableStream.blob()`\n\nafter the`Response`\n\nbody was already consumed, now properly rejects with`ERR_BODY_ALREADY_USED`\n\n- Fixed: a crash in\n`Request.formData()`\n\n/`Response.formData()`\n\n/`Blob.formData()`\n\nwhen the`Content-Type`\n\nheader contained a malformed boundary value (@dylan-conway) - Fixed: HTTP server now correctly rejects requests with conflicting duplicate\n`Content-Length`\n\nheaders 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)\n- Fixed: edge case crash formatting error messages when\n`Symbol.toPrimitive`\n\nthrows - Fixed: a crash that could occur when a stack overflow happened during error message formatting\n\n[JavaScript bundler](#javascript-bundler)\n\n- Fixed:\n`bun build --compile`\n\non NixOS and Guix producing executables that only ran on the exact same Nix generation, because the compiled binary inherited a`/nix/store/...`\n\nELF interpreter path.`PT_INTERP`\n\nis now normalized back to the standard FHS path so compiled binaries are portable across Linux systems. (@Jarred-Sumner) - Fixed a crash in\n`bun build --compile`\n\nwhen CSS files are passed as entry points alongside JS/TS entry points\n\n[bun test](#bun-test)\n\n- Fixed:\n`mock.module()`\n\ncould crash when the first argument is not a string - Fixed: a crash that could occur when\n`mock.module()`\n\ntriggered auto-install during module resolution - Fixed: potential crash in\n`expect.extend`\n\nwith certain invalid inputs - Fixed:\n`--elide-lines`\n\nflag 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)\n\n[Bun Shell](#bun-shell)\n\n- Fixed:\n`Bun.$.braces()`\n\ncould crash when called with an empty string\n\n[Windows](#windows)\n\n- Fixed: tar archive extraction on Windows could write files outside the extraction directory when an entry contained an absolute path (e.g.\n`C:\\...`\n\nor UNC paths) — these entries are now skipped (@dylan-conway)", "url": "https://wpnews.pro/news/bun-v1-3-12", "canonical_source": "https://bun.com/blog/bun-v1.3.12", "published_at": "2026-04-09 00:00:00+00:00", "updated_at": "2026-05-22 20:39:49.524646+00:00", "lang": "en", "topics": ["developer-tools", "open-source"], "entities": ["Bun", "WebKit", "Chrome", "Playwright", "Jarred Sumner"], "alternates": {"html": "https://wpnews.pro/news/bun-v1-3-12", "markdown": "https://wpnews.pro/news/bun-v1-3-12.md", "text": "https://wpnews.pro/news/bun-v1-3-12.txt", "jsonld": "https://wpnews.pro/news/bun-v1-3-12.jsonld"}}