cd /news/developer-tools/bun-v1-3-11 · home topics developer-tools article
[ARTICLE · art-9950] src=bun.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Bun v1.3.11

Bun v1.3.11 reduces its Linux x64 binary size by 4 MB by removing CMake, and introduces a new `Bun.cron` API for cross-platform OS-level cron job management. The update also adds `Bun.sliceAnsi`, a high-performance built-in for ANSI-aware string slicing that replaces the `slice-ansi` and `cli-truncate` npm packages. Additionally, `Bun.markdown.render()` now provides richer metadata to list-related callbacks, enabling custom list numbering without workarounds.

read16 min views21 publishedMar 18, 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

4 MB smaller on Linux x64

In the next version of Bun

— Bun (@bunjavascript)

Bun gets 4 MB smaller on Linux x64 because we deleted CMake[pic.twitter.com/HQPacwNO3O][March 14, 2026]

Bun.cron

— OS-level Cron Jobs and Expression Parsing

Bun.cron

— OS-level Cron Jobs and Expression ParsingBun now includes a built-in Bun.cron

API for registering OS-level cron jobs, parsing cron expressions, and removing scheduled jobs — all cross-platform.

Register a cron job

// Register a cron job (crontab on Linux, launchd on macOS, Task Scheduler on Windows)
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");

When the OS scheduler fires, Bun imports the script and calls default.scheduled()

, following the Cloudflare Workers Cron Triggers API:

// worker.ts
export default {
  async scheduled(controller) {
    // controller.cron === "30 2 * * 1"
    // controller.scheduledTime === 1737340200000
    await doWork();
  },
};

Parse cron expressions

const next = Bun.cron.parse("*/15 * * * *"); // next quarter-hour
const weekday = Bun.cron.parse("0 9 * * MON-FRI"); // next weekday at 9 AM UTC
const yearly = Bun.cron.parse("@yearly"); // next January 1st

// Chain calls to get a sequence
const first = Bun.cron.parse("0 * * * *", from);
const second = Bun.cron.parse("0 * * * *", first);

Returns a Date

or null

if no match exists within ~4 years (e.g. February 30th).

Remove a job

await Bun.cron.remove("weekly-report");

Cron expression features

Standard 5-field format with*

,,

,-

,/

operatorsNamed days and months:MON

SUN

,JAN

DEC

(case-insensitive, full names supported)Nicknames:@yearly

,@monthly

,@weekly

,@daily

,@hourly

POSIX OR logic: when both day-of-month and day-of-week are restricted, either matching fires the job** Sunday as 7**: weekday field accepts both0

and7

Platform backends

Platform Backend Logs
Linux crontab journalctl -u cron
macOS launchd plist /tmp/bun.cron.<title>.{stdout,stderr}.log
Windows schtasks Event Viewer → TaskScheduler

Re-registering with the same title overwrites the existing job in-place.

Bun.sliceAnsi

— ANSI & grapheme-aware string slicing

Bun.sliceAnsi

— ANSI & grapheme-aware string slicingA new built-in that replaces both the slice-ansi and

npm packages.

cli-truncate

Bun.sliceAnsi

slices strings by terminal column width while preserving ANSI escape codes (SGR colors, OSC 8 hyperlinks) and respecting grapheme cluster boundaries (emoji, combining marks, flags).

// Plain slice (replaces slice-ansi)
Bun.sliceAnsi("\x1b[31mhello\x1b[39m", 1, 4); // "\x1b[31mell\x1b[39m"

// Truncation with ellipsis (replaces cli-truncate)
Bun.sliceAnsi("unicorn", 0, 4, "…"); // "uni…"
Bun.sliceAnsi("unicorn", -4, undefined, "…"); // "…orn"

The ellipsis is emitted inside active SGR styles (inherits color/bold) so truncated text stays visually consistent, but outside hyperlinks to avoid broken links. Negative indices are supported for slicing from the end.

An optional { ambiguousIsNarrow }

option is available for controlling East Asian ambiguous width behavior, matching Bun.stringWidth

and Bun.wrapAnsi

.

Performance

Uses a three-tier dispatch strategy:

SIMD ASCII fast path— pure ASCII input is handled with a single SIMD scan and zero-copy when nothing is cut** Single-pass streaming**— non-negative indices (the common case) walk the input once with stack-only allocation** Two-pass for negative indices**— computes total width first, then emits

Bun.markdown.render()

now passes richer metadata to listItem

and list

callbacks

Bun.markdown.render()

now passes richer metadata to listItem

and list

callbacksThe listItem

callback in Bun.markdown.render()

now receives index

, depth

, ordered

, and start

in its metadata object, and the list

callback now includes depth

. Previously, listItem

only received checked

(and only for task list items), making it impossible to know an item's position, nesting level, or parent list type without workarounds.

This makes it straightforward to implement custom list markers like nested numbering schemes (1.

/ a.

/ i.

) without regex post-processing:

const result = Bun.markdown.render(
  "1. first\n   1. sub-a\n   2. sub-b\n2. second",
  {
    listItem: (children, { index, depth, ordered, start }) => {
      const n = (start ?? 1) + index;
      const marker = !ordered
        ? "-"
        : depth === 0
        ? `${n}.`
        : `${String.fromCharCode(96 + n)}.`;
      return "  ".repeat(depth) + marker + " " + children.trimEnd() + "\n";
    },
    list: (children) => "\n" + children,
  },
);
// 1. first
//   a. sub-a
//   b. sub-b
// 2. second

The full listItem

meta shape is now:

Property Type Description
index number 0-based position within the parent list
depth number Nesting level of the parent list (0 = top-level)
ordered boolean Whether the parent list is ordered
start `number undefined`
checked `boolean undefined`

Breaking change: The listItem

callback now always receives the meta object (previously it was only passed for task list items). start

and checked

are undefined

when not applicable, keeping the object shape fixed for monomorphic inline caches.

Meta objects use cached JSC structures internally, adding ~0.7ns per object overhead — effectively free.

--path-ignore-patterns

for bun test

--path-ignore-patterns

for bun test

You can now exclude files and directories from test discovery using glob patterns with the new --path-ignore-patterns

flag or test.pathIgnorePatterns

in bunfig.toml

.

This is useful when your project contains submodules, vendored code, or other directories with *.test.ts

files that you don't want bun test

to pick up. Matched directories are pruned during scanning, so their contents are never traversed — ignoring a large directory tree is efficient.

[test]
pathIgnorePatterns = [
  "vendor/**",
  "submodules/**",
  "fixtures/**",
  "**/test-data/**"
]

Or via the command line:

bun test --path-ignore-patterns 'vendor/**' --path-ignore-patterns 'fixtures/**'

Command-line --path-ignore-patterns

flags override the bunfig.toml

value entirely — the two are not merged.

Thanks to @ctjlewis and @alii for the contribution!

Fixed dgram

UDP socket bugs on macOS

dgram

UDP socket bugs on macOSFixed three bugs in UDP socket creation that caused dgram

sockets to silently fail on macOS:

: ThereusePort

now works on macOSreusePort: true

option was previously gated to Linux only. It now works on any platform that supportsSO_REUSEPORT

.Implicit bind-on-send works on macOS: Callingsend()

on an unbound UDP socket should implicitly bind to0.0.0.0

on a random port (matching Node.js behavior). Hardcoded Linux errno values (92

instead of theENOPROTOOPT

macro) prevented IPv4 fallback paths from being taken on macOS, whereENOPROTOOPT

is42

.Socket fd leak on failure: Whensetsockopt

for reuse failed, the socket file descriptor was leaked and no diagnostic errno was propagated, producing generic "Failed to bind socket" errors.

This affected real-world libraries like k-rpc (used by

) that rely on implicit binding behavior.

bittorrent-dht

import dgram from "dgram";

const socket = dgram.createSocket("udp4");

// This now correctly auto-binds on macOS, matching Node.js behavior
socket.send(Buffer.from("hello"), 0, 5, 41234, "127.0.0.1", (err) => {
  if (err) console.error("send error:", err);
  else console.log("sent successfully");

  const addr = socket.address();
  console.log("bound to:", addr); // Shows auto-assigned port
  socket.close();
});

Native ARM64 shim for Windows node_modules/.bin

binaries

node_modules/.bin

binariesThe bun_shim_impl.exe

used for node_modules/.bin/*

on Windows was previously hardcoded to x86_64, meaning every package binary invocation on Windows ARM64 ran under x64 emulation. The shim is now compiled natively for aarch64 when building Bun for Windows ARM64, removing the emulation overhead.

Thanks to @dylan-conway for the contribution!

Bugfixes

Node.js compatibility improvements

  • Fixed: An operator precedence bug in native readable streams caused 0 < MIN_BUFFER_SIZE

to be evaluated before the??

operator, resulting in excessive memory allocation when processing stream chunks - Fixed: Custom lookup

function in Node.jshttp

/https

client (e.g. via axios) breaking TLS certificate verification withERR_TLS_CERT_ALTNAME_INVALID

orunknown certificate verification error

. When a custom DNS lookup resolved a hostname to an IP address, the original hostname was lost, causing TLS SNI and certificate verification to fail. - Fixed: fs.openSync

andfs.promises.open

throwingEINVAL

on Windows when passed numeric flags fromfs.constants

(e.g.O_CREAT | O_TRUNC | O_WRONLY

). Thefs.constants

values on Windows use native MSVC values which differ from Bun's internal representation, causing flags likeO_CREAT

to be silently dropped. This also fixesUV_FS_O_FILEMAP

support, which unbreaks packages liketar@7

(used bynpm

,giget

, and others) that silently failed to extract files on Windows. (@Hona) - Fixed: fs.stat

truncating sub-millisecond precision frommtimeMs

,atimeMs

,ctimeMs

, andbirthtimeMs

properties, now returning fractional milliseconds matching Node.js behavior - Fixed: fs.watch()

crash on Windows when a failed watch is retried on the same path - Fixed: a crash in fs.watchFile

  • Fixed: node:fs

functions crashing with certain inputs - Fixed: crash ( cast causes pointer to be null

) on Windows whenfs.realpathSync

orfs.readlink

encountered certain edge-case filesystem configurations like ramdisk volumes, substituted drives, or standalone executables in unusual locations - Fixed: console.Console

returningundefined

permanently after being accessed during a near-stack-overflow condition (@sosukesuzuki) - Fixed: Buffer.compare

not properly validatingtargetEnd

andsourceEnd

offset bounds, where certain combinations of start/end values could bypass range checks instead of throwingERR_OUT_OF_RANGE

as Node.js does - Fixed: rare crash in Buffer.indexOf

,Buffer.lastIndexOf

, andBuffer.includes

  • Fixed: buffer overflow in path-handling code
  • Fixed: process.off("SIGxxx", handler)

removing one of multiple signal listeners would incorrectly uninstall the OS signal handler, causing remaining listeners to stop receiving signals - Fixed: execFileSync

andexecSync

errors containing a self-referencing cycle (err.error === err

) that causedJSON.stringify(err)

to throw - Fixed: spawnSync

could accidentally drain the global microtask queue, executing user JavaScript during what should be a synchronous, blocking call. - Fixed: missing BoringSSL error clearing in crypto.createPrivateKey()

  • Fixed: Crash in crypto.Hash

update()

/digest()

caused by missingthis

validation, a GC hazard on input strings during encoding conversion, and reading from detached buffers. Now properly throwsERR_INVALID_THIS

andERR_INVALID_STATE

instead of crashing. - Fixed: crashs when calling native crypto/stream prototype methods (like Hmac.digest()

,DiffieHellmanGroup.verifyError

) with an invalidthis

value — now correctly throwsERR_INVALID_THIS

instead of crashing - Fixed: X509Certificate.prototype

wasundefined

, which prevented subclassing withclass Foo extends X509Certificate {}

(@sosukesuzuki) - Fixed: getPeerCertificate()

returningundefined

instead of{}

when no peer certificate is available, which causedcheckServerIdentity()

to crash withTypeError: Cannot destructure property 'subject' from null or undefined value

during TLS handshakes — most notably when connecting to MongoDB Atlas clusters - Fixed: hypothetical out of bounds read/write in native zlib write()

/writeSync()

where user-controlled offset and length parameters were not properly validated in production builds. These now correctly throwERR_OUT_OF_RANGE

,ERR_INVALID_ARG_TYPE

, and other appropriate errors. - Fixed: dgram.createSocket()

incorrectly setSO_REUSEADDR

on all UDP sockets, allowing multiple processes to silently bind to the same port without throwingEADDRINUSE

— diverging from Node.js behavior.SO_REUSEADDR

is now only applied whenreuseAddr: true

is explicitly passed. - Fixed: a latent GC safety issue in node:vm

modules where a garbage collection cycle during object construction could lead to a crash (@sosukesuzuki) - Fixed: structuredClone()

throwingDataCloneError

on objects created vianapi_create_object

, matching Node.js behavior (@dylan-conway) - Fixed: node:http2

client streams stalling after receiving 65,535 bytes when usingsetLocalWindowSize()

— the method updated the internal connection window size but never sent aWINDOW_UPDATE

frame to the peer, causing the server to stop sending data once the default window was exhausted - Fixed: node:http2

getPackedSettings

,getUnpackedSettings

, andgetDefaultSettings

now match Node.js behavior, including support forenableConnectProtocol

,customSettings

, correctenablePush

default, and properERR_HTTP2_INVALID_SETTING_VALUE

error codes (@cirospaciari) - Fixed: crash that could occur on exit when using NAPI native addon modules (such as skia-canvas

) on Windows - Fixed: bug in libuv on Windows that could cause pipe data loss when reading from subprocess pipes

Bun APIs

  • Fixed PgBouncer incompatibility with sql.prepare(false)

queries - Fixed: Bun.file().text()

and similar async read methods not keeping event loop alive when an error errors - Fixed: Incorrect Bun.file()

async read error paths on Windows - Fixed: Bun.file().stat()

andBun.file().delete()

corrupting file paths containing non-ASCII UTF-8 characters (e.g., German umlauts, Japanese characters, emoji), causingENOENT

errors due to double-encoding - Fixed: Bun.stdin.stream()

andBun.stdin.text()

returning empty on Linux after callingBun.stdin.exists()

or accessingBun.stdin.size

  • Fixed: Bun.JSONL.parseChunk(input, start, end)

now honorsstart

/end

offsets wheninput

is a string. - Fixed: S3File.slice(0, N).stream()

ignoring the slice range and down the entire file instead of only the requested byte range - Fixed: bun:sql

panicking with "integer does not fit in destination type" when a PostgreSQL query exceeded the 65,535 parameter limit (e.g. batch inserting 7,000 rows × 10 columns). Now throws a descriptivePostgresError

with codeERR_POSTGRES_TOO_MANY_PARAMETERS

and a hint to reduce batch size - Fixed: Valkey RESP protocol parser no longer crashes with a stack overflow on deeply nested server responses by enforcing a maximum nesting depth of 128 for aggregate types (@dylan-conway)

  • Fixed: Bun.Transpiler

ignoringexperimentalDecorators: true

andemitDecoratorMetadata: true

from tsconfig, always emitting TC39-style decorators instead of legacy TypeScript decorators. This broke frameworks like Angular that rely on legacy decorator calling conventions in JIT mode. - Fixed: Bun.Transpiler.scanImports()

andBun.Transpiler.scan()

ignoring thetrimUnusedImports

option — unused imports were only being trimmed bytransformSync()

  • Fixed: memory leak in Bun.Transpiler

when using a customtsconfig

with asynctransform()

calls. After the firstawait transpiler.transform()

, subsequent calls could read freed memory or double-free the tsconfig pointer, potentially causing crashes. - Fixed: emitDecoratorMetadata: true

in tsconfig.json withoutexperimentalDecorators: true

incorrectly used TC39 standard decorators instead of legacy decorator semantics, causing NestJS, TypeORM, Angular, and other legacy-decorator frameworks to crash withdescriptor.value

undefined - Fixed: Breakpoints landing at wrong line numbers when debugging files over 50KB in VSCode's debug terminal ( BUN_INSPECT

env var). The runtime transpiler cache was not being disabled when the debugger was activated via theBUN_INSPECT

environment variable (as opposed to--inspect

CLI flags), causing cached output without inline source maps to be used. (@alii) - Fixed: HTML-referenced assets (favicons, images, etc.) returning 404 when served with Bun.serve()

because they were missing from the bundle manifest'sfiles

array despite being correctly emitted to disk - Fixed ReadableStream

withtype: "direct"

incorrectly calling the user'scancel

callback on normal stream completion - Fixed: Empty string arguments in Bun shell

  • Fixed: DoS in Bun.stringWidth

  • Fixed: Bun.stringWidth

grapheme bug: ANSI escape bytes were incorrectly included in grapheme break tracking

Web APIs

  • Fixed: incorrect value in WebSocket.prototype.protocol

in certain cases - Fixed: ws.ping()

andws.pong()

called without arguments incorrectly sent non-empty payloads instead of empty control frames, causing disconnections with strict WebSocket servers (e.g. Binance) that validate pong payloads match ping payloads (@gaowhen) - Fixed: WebSocket client now validates the Sec-WebSocket-Accept

header value during the upgrade handshake per RFC 6455 §4.2.2, rejecting connections where the server returns a stale or mismatched response - Fixed: Request.formData()

truncating small binary files with null bytes - Fixed: a crash when calling fetch()

using a very large number of headers - Fixed: crash in fetch with TLS proxies in certain cases

  • Fixed: Edgecase with pipelined HTTP requests with no headers
  • Fixed: HTTP header value incorrect stripping for certain cases
  • Hardened Bun's HTTP server against malformed chunked transfer requests. Thanks to @sim1222 for reporting issues.
  • Fixed: CRLF injection vulnerability in ServerResponse.prototype.writeEarlyHints

where header names and values were written to the socket without validation, allowing HTTP response splitting attacks - Fixed: WebSocket connections dropping over proxy tunnels with bidirectional traffic

bun install

  • Fixed: bun install

hanging indefinitely or silently skipping processing when a security scanner is enabled and the project has more than ~790 packages. The package list is now sent to the scanner subprocess via an IPC pipe instead of command-line arguments, avoiding OS argument length limits - Fixed: bun install

hanging indefinitely (~300 seconds per dependency) when using an HTTP proxy (http_proxy

/https_proxy

) with cached packages that return304 Not Modified

through a CONNECT tunnel (@WhiteMinds) - Fixed: Non-deterministic bun install

bug where transitive peer dependencies were left unresolved when all manifest loads were synchronous (e.g., warm cache with validCache-Control: max-age

), causing missing peer dependency symlinks with--linker=isolated

(@dylan-conway) - Fixed: .npmrc

auth token matching only comparing hostnames - Fixed: bun install

silently exiting with code 1 when the security scanner encounters an error, making failures impossible to debug (especially in CI). All error paths now print descriptive messages to stderr. - Fixed: bun install

now shows an accurate error message when afile:

dependency path is missing (e.g., due to a stale lockfile), instead of the misleading "Bun could not find a package.json file to install from" message. The new error clearly identifies the dependency and the exact path that was not found. - Fixed: bun update -i

select all ('A' key) now correctly updates packages instead of showing "No packages selected for update" - Fixed: bun pack

andbun publish

using staleversion

andname

frompackage.json

when lifecycle scripts (prepublishOnly

,prepack

,prepare

) modify them during execution. Previously, the tarball filename and publish registry metadata would use the original values captured before scripts ran. (@dylan-conway) - Fixed: bun bun.lockb | head

no longer prints an internalBrokenPipe

error message instead of exiting silently

JavaScript bundler

  • Fixed: bun build --compile --bytecode --format=esm

crashing at runtime with "Cannot find module" errors when barrel-optimized packages (those with"sideEffects": false

) had unused re-exports recorded as external dependencies in bytecode ModuleInfo (@Jarred-Sumner) - Fixed: import Bun from 'bun'

returningundefined

when bundled with--bytecode

flag. The CJS lowering pass was incorrectly generatingglobalThis.Bun.default

instead ofglobalThis.Bun

for default imports. - Fixed: Bundler dropping exports when barrel files with sideEffects: false

re-export namespace imports (import * as X from './mod'; export { X }

), causingReferenceError

for the re-exported bindings at runtime - Fixed: Bundler barrel optimization incorrectly dropped exports needed by dynamic import()

when the same barrel was also referenced by a static named import, producing invalid JS with undeclared export bindings (SyntaxError

at runtime). This commonly affected AWS SDK builds using@smithy/credential-provider-imds

. (@dylan-conway) - Fixed: dynamic import()

with import attributes (e.g.{ with: { type: 'text' } }

) not applying the correct during bundling, which caused--compile

builds to fail withrequire_* is not defined

for.html

files imported as text - Fixed: bun build --compile

with HTML routes producing relative asset URLs (./chunk-abc.js

) that broke when served from nested routes like/foo/

— assets now use absolute root-relative URLs (/chunk-abc.js

) - Fixed: Crash when resolving very long import paths (e.g. through tsconfig baseUrl

,paths

wildcards, ornode_modules

lookups) - Fixed: Dev server barrel optimizer incorrectly deferring export *

target submodules, causing classes likeQueryClient

to becomeundefined

and throwingTypeError: Right-hand side of 'instanceof' is not an object

— affected packages like@refinedev/core

with@tanstack/react-query

CSS Parser

  • Fixed: CSS bundler incorrectly mapping logical border-radius properties ( border-start-start-radius

,border-start-end-radius

,border-end-start-radius

,border-end-end-radius

) to onlyborder-top-left-radius

/border-top-right-radius

when values containvar()

references. All four logical properties now correctly resolve to their distinct physical counterparts. - Fixed: CSS mask

shorthand parsing incorrectly droppinggeometry-box

values likepadding-box

andcontent-box

, which could also cause rules with different geometry boxes to be incorrectly merged (@anthonybaldwin) - Fixed: unicode-range

values in@font-face

rules being mangled by the CSS processor (e.g.,U+0000-00FF

becameU0-0FF

), which caused browsers to silently ignore the entire@font-face

rule and fonts to not load

bun test

  • Fixed: bun test

object diffs andconsole.log

silently dropping properties with empty string keys (""

)

Bun Shell

  • Fixed: shell interpolation could crash given invalid input
  • Fixed: Bun's builtin rm

in the shell returning exit code 0 instead of the correct non-zero exit code when a file doesn't exist and.quiet()

or.text()

is used. This also caused errors to never be thrown in those code paths.

TypeScript types

  • Fixed: Missing contentEncoding

property in theS3Options

TypeScript type definition, which caused TypeScript errors and missing IDE autocompletion when using thecontentEncoding

option added in Bun v1.3.7

Runtime and CLI

  • Fixed: bun run --filter

andbun run --workspaces

failing when theNODE

environment variable points to a non-existent file. Bun now validates that the path is an executable before using it, and falls back to searchingPATH

or creating its own node symlink. - Fixed: 100% CPU spin on Linux caused by the inotify file watcher re-parsing stale events in an infinite loop when a single read()

returned more than 128 events - Fixed crashes when passing Proxy-wrapped arrays to built-in APIs (@sosukesuzuki)

── 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-11] indexed:0 read:16min 2026-03-18 ·