bun · diff
git:20260314.9629b35 to git:20260902.915f96c
263 added, 167 removed. Audit B to A.
---
name: bun
description: >-
- Bun runtime conventions, APIs, and toolchain. Invoke whenever task involves any
- interaction with Bun — serving HTTP, file I/O, shell scripting, testing, bundling,
- or package management with Bun.
+ Write and review Bun: the Bun-native API surface against its `node:` equivalents, the HTTP and WebSocket server, file
+ and process I/O, the shell, the bundled data clients, `bun:test`, the bundler and single-file executables, the package
+ manager, and `bunfig.toml`.
+ when_to_use: >-
+ Invoke whenever Bun code or a Bun project is touched at all — writing, reviewing, refactoring, debugging, or
+ configuring one, or deciding whether a `Bun.*` API replaces a dependency. Also invoke on the symptoms: a request dies
+ after ten seconds with no error, a stack trace or an error page reaches the browser, `postinstall` stops running for a
+ package that used to build, a mocked module still ran its side effects, `bun install` lays out `node_modules`
+ differently than the last machine did, a compiled binary reads a config file it should not, or a `node:` API behaves
+ differently under Bun than under Node. Covers the Bun runtime and its toolchain, `bun:test` included; JavaScript
+ semantics belong to the javascript skill, types to typescript, Node's own APIs to nodejs, and Vitest to vitest.
+ compatibility: Uses Claude Code frontmatter beyond the Agent Skills spec (when_to_use)
---
- # Bun
-
- **Use Bun APIs, not Node.js polyfills. If Bun provides a native API for it, use it.**
-
- Bun is a batteries-included JavaScript runtime. It replaces Node.js, npm, Jest, and webpack with a single tool. Prefer
- Bun-native APIs (`Bun.serve`, `Bun.file`, `Bun.$`, `bun:sqlite`, `bun:test`) over Node.js equivalents unless portability
- is an explicit requirement.
-
- ## References
-
- - **HTTP server** — [`${CLAUDE_SKILL_DIR}/references/server.md`]: Route types, file response patterns, WebSocket
- pub/sub, server config
- - **File I/O and processes** — [`${CLAUDE_SKILL_DIR}/references/io-and-processes.md`]: File I/O details, shell API,
- child processes, workers
- - **Testing** — [`${CLAUDE_SKILL_DIR}/references/testing.md`]: Test modifiers, parametrized tests, mocking, snapshots,
- CLI flags
- - **SQLite, bundler, plugins** — [`${CLAUDE_SKILL_DIR}/references/ecosystem.md`]: SQLite API, bundler options, plugins,
- macros
- - **Configuration** — [`${CLAUDE_SKILL_DIR}/references/config-and-compat.md`]: bunfig.toml sections, Node.js
- compatibility, env vars
-
- ## Prefer Bun-Native APIs
-
- Rule: if `Bun.*` or `bun:*` has it, use it. Fall back to `node:*` only when there's no Bun-native alternative or when
- portability to Node.js is required.
-
- Core mappings: `Bun.serve()` over `http.createServer()`, `Bun.file()`/`Bun.write()` over `node:fs`, `Bun.$` over
- `child_process.exec`, `bun:sqlite` over `better-sqlite3`, `bun:test` over Jest/Vitest, `Bun.password` over `bcrypt`,
- `Bun.sleep()` over `setTimeout` wrappers, `Bun.spawn()` over `child_process.spawn`. Use `node:fs` for directory ops — no
- Bun API yet. Use Web Streams API over `node:stream`.
-
- Full API preference table: see `${CLAUDE_SKILL_DIR}/references/io-and-processes.md`.
-
- ## HTTP Server
-
- ### Routing
-
- - **Use `routes` object** (v1.2.3+) for declarative path matching. Preferred over `fetch`-based routing.
- - **Route types:** exact (`"/users/all"`, highest priority), parameterized (`"/users/:id"`, `req.params.id`), wildcard
- (`"/api/*"`), per-method (`{ GET: handler, POST: handler }`).
- - **Precedence:** exact > parameterized > wildcard > global catch-all.
- - **`fetch` handler** as fallback for unmatched routes, not primary routing.
- - **Always implement `error` handler** in `Bun.serve()`.
- - **`development: true`** in dev for built-in error pages.
-
- ### Static Responses
-
- - **Use static `Response` objects** for health checks, redirects, fixed JSON — they are zero-allocation after init,
- cached for server lifetime.
- - **Call `server.reload()`** to update static responses at runtime.
-
- ### Request Object
-
- - **Route handlers receive `BunRequest`** (extends `Request`) with `params` (auto URL-decoded) and `cookies`
- (auto-tracked `CookieMap`).
- - **TypeScript infers param shape** when route is a string literal.
- - **Cookie changes are auto-tracked** — `Set-Cookie` headers added automatically when using `req.cookies.set()` /
- `.delete()`.
-
- ### WebSocket
-
- - **Upgrade via `server.upgrade(req, { data })`** in the `fetch` handler.
- - **Use native pub/sub** for topic-based broadcasting: `ws.subscribe("topic")`, `ws.publish("topic", data)`.
- - **Type `ws.data`** via the `data` property on the `websocket` handler object.
+ Bun's value is that the runtime, the toolchain, and the batteries are one binary. Three biases decide most calls:
- WebSocket limits, server configuration, file response patterns, HTML imports, and server lifecycle details: see
- `${CLAUDE_SKILL_DIR}/references/server.md`.
+ - **Reach for what already ships.** A dependency earns its place by doing what `Bun.*`, `bun:*`, and the bundled
+ toolchain do not. Adding one that duplicates a built-in is a defect.
+ - **Write `node:` only where Bun has no native equivalent, or where the code must also run under Node.** Portability is
+ a requirement to state, not a habit to keep.
+ - **Bun's defaults favor the fast path, and several fail silently.** Read the default before depending on it.
- ## File I/O
+ ## Release Version
- - **`Bun.file()` is lazy.** Creating a `BunFile` does not read from disk. It conforms to `Blob`.
- - **Read with `.text()`, `.json()`, `.bytes()`, `.stream()`, `.arrayBuffer()`** on `BunFile`.
- - **Check existence:** `await file.exists()`. Access `file.size` and `file.type`.
- - **`Bun.write()` handles all types** — string, Blob, Response, ArrayBuffer, BunFile. Uses fastest syscall per platform
- (`copy_file_range`, `sendfile`, `clonefile`).
- - **Incremental writing:** use `file.writer()` (`FileSink`). Call `.flush()` to flush buffer, `.end()` to flush + close
- (required to let process exit).
- - **Built-in stdio references:** `Bun.stdin` (readonly), `Bun.stdout`, `Bun.stderr`.
- - **Use `node:fs` for directory ops** — `mkdir`, `readdir`. No Bun-specific API yet.
- - **`import.meta.dir`** gives the directory of the current file.
+ Bun ships one stable line and no long-lived support branches, so `bun upgrade` moves a machine to whatever release is
+ newest. Pin the version in CI (`oven-sh/setup-bun`, `bun-version`) so a developer machine and a runner agree, and state
+ the release a feature needs in the sentence that uses it. Never write "the latest version supports X".
- ## Shell API — `Bun.$`
+ The rules below assume 1.3.0 or later unless a version anchor says otherwise. Floors for the features these rules
+ reference:
- Cross-platform bash-like shell with JavaScript interop. Runs in-process (not `/bin/sh`).
+ - **1.2.3** — the `routes` object in `Bun.serve`
+ - **1.3.0** — `Bun.sql` for MySQL, MariaDB, and SQLite; `Bun.redis`; `Bun.secrets`; `Bun.YAML`; catalogs;
+ `test.concurrent` and `test.serial`; `--randomize`; `expectTypeOf`
+ - **1.3.2** — the isolated linker as the default for new monorepos; `nativeDependencies`; `ignoreScripts`
+ - **1.3.3** — `test(..., { retry })`; `Bun.JSON5`, `Bun.JSONL`, `Bun.JSONC`, `Bun.XML`, `Bun.Archive`, `URLPattern`
+ - **1.3.4** — `jest.useFakeTimers()`; `--compile` stops auto-loading `tsconfig.json` and `package.json`
+ - **1.3.5** — `Bun.Terminal`; `bun:bundle` feature flags; `trustedDependencies` limited to npm sources
+ - **1.3.6** — `Bun.build({ files })`; `metafile: true`
+ - **1.3.8** — `Bun.markdown`; `--metafile-md`
+ - **1.3.9** — `bun run --parallel`; bytecode for ES modules
+ - **1.3.10** — `optimizeImports`; standard TC39 decorators; `--compile --target=browser` for a single HTML file
+ - **1.3.11** — `Bun.cron()`
+ - **1.3.13** — `bun test --parallel`, `--isolate`, `--shard`, `--changed`; `Range` and conditional requests in
+ `Bun.serve`
+ - **1.3.14** — `Bun.Image`; the global virtual store; HTTP/3 in `Bun.serve`; HTTP/2 and HTTP/3 in `fetch()`
+ - **1.4.0** — Node 26 compatibility; directory routes; `bun audit fix`, `bun dedupe`, `bun prune`, `bun pm diff`,
+ `bun pm licenses`; `bun test --timings`; `--asset`; `lockfileVersion: 2`
- - **`$` tagged template** for shell commands. Interpolated values are auto-escaped — injection-safe by default.
- - **Read output:** `.text()` (string, auto-quiets), `.json()` (parsed), `.lines()` (async iterator), `.blob()`, or
- `await $\`...\``for`{ stdout, stderr }` Buffers.
- - **`.quiet()`** to suppress stdout/stderr output.
- - **Non-zero exit codes throw `ShellError`** by default. Use `.nothrow()` to handle exit codes manually. Configure
- globally: `$.nothrow()` or `$.throws(false)`.
- - **Piping and redirection** work: `|`, `>`, `2>&1`, `< ${Bun.file("input.txt")}`, `< ${response}`.
- - **Set environment/cwd:** `.env({ FOO: "bar" })`, `.cwd("/tmp")`. Global defaults: `$.env(...)`, `$.cwd(...)`.
- - **Security:** interpolated variables are escaped (no command injection), but argument injection is still possible
- (external commands interpret their own flags). Spawning `bash -c` bypasses Bun's protections.
+ ## Prefer the Built-in
- ## Child Processes
+ Reach for the built-in, and drop the package it replaces from `package.json`.
- - **`Bun.spawn()`** for fine-grained async process control. Access `proc.pid`, `proc.stdout`, `proc.exited`,
- `proc.exitCode`. Kill with `proc.kill()`.
- - **`Bun.spawnSync()`** for blocking execution. Rule: `spawnSync` for CLI tools, `spawn` for servers.
- - **Timeout and abort:** `{ timeout: 5000, killSignal: "SIGKILL" }` or pass `AbortController.signal`.
- - **IPC between Bun processes:** `Bun.spawn(["bun", "child.ts"], { ipc(message) {} })`.
+ - **HTTP server** — `Bun.serve()` over `http.createServer()`, Express, or Fastify
+ - **Files** — `Bun.file()` and `Bun.write()` over `node:fs` read and write helpers
+ - **Directories** — `node:fs`. There is no `Bun.*` equivalent for `mkdir` or `readdir`
+ - **Globbing** — `Bun.Glob` over `glob` and `fast-glob`
+ - **Shelling out** — `Bun.$` over `child_process.exec`, `zx`, `execa`, `cross-env`, and `rimraf`
+ - **Processes** — `Bun.spawn` and `Bun.spawnSync` over `child_process.spawn`
+ - **Pseudo-terminals** — `Bun.Terminal` over `node-pty` (1.3.5)
+ - **SQLite** — `bun:sqlite` over `better-sqlite3`
+ - **Postgres, MySQL, MariaDB** — `Bun.sql` over `pg` and `mysql2` (1.3.0)
+ - **Redis and Valkey** — `Bun.redis` over `ioredis` (1.3.0)
+ - **S3-compatible storage** — `Bun.S3Client` over `@aws-sdk/client-s3`
+ - **Password hashing** — `Bun.password` over `bcrypt` and `argon2`
+ - **Testing** — `bun:test` over Jest
+ - **Bundling** — `Bun.build()` and `bun build` over webpack, Rollup, and esbuild
+ - **Images** — `Bun.Image` over `sharp` (1.3.14)
+ - **Headless browsing** — `Bun.WebView` over Puppeteer (1.3.12)
+ - **Markdown** — `Bun.markdown` over `marked` (1.3.8)
+ - **Scheduling** — `Bun.cron()` over `node-cron` (1.3.11)
+ - **Running scripts concurrently** — `bun run --parallel` over `npm-run-all` and `concurrently` (1.3.9)
+ - **Config formats** — `Bun.TOML`, `Bun.YAML`, `Bun.JSON5`, `Bun.JSONC`, `Bun.XML` over `@iarna/toml`, `js-yaml`,
+ `json5`, `jsonc-parser`, and `fast-xml-parser`
+ - **Newline-delimited JSON** — `Bun.JSONL` over `ndjson`
+ - **Tarballs** — `Bun.Archive` over `tar`
+ - **Terminal string width** — `Bun.stringWidth`, `Bun.sliceAnsi`, `Bun.wrapAnsi` over `string-width`, `slice-ansi`,
+ `wrap-ansi`, and `cli-truncate`
+ - **Route patterns** — `URLPattern` over `path-to-regexp`
+ - **Credential storage** — `Bun.secrets` over a plaintext dotfile, for local development tools
+ - **Sleeping, hashing, comparison, compression** — `Bun.sleep`, `Bun.hash`, `Bun.CryptoHasher`, `Bun.deepEquals`,
+ `Bun.gzipSync`, `Bun.zstdCompressSync`
- Stdin/stdout options, workers, and process details: see `${CLAUDE_SKILL_DIR}/references/io-and-processes.md`.
+ Two boundaries hold in the other direction. **Web Streams are the native currency** — `Bun.file().stream()`,
+ `Response.body`, and `proc.stdout` are all `ReadableStream`, so `node:stream` is for code shaped around Node, not the
+ default. **`crypto.randomUUID()` is the standard v4 UUID**; `Bun.randomUUIDv7()` is the time-ordered variant, not a
+ replacement for it.
- ## Testing — `bun:test`
+ ## HTTP Server
- - **Import from `bun:test`**, not `jest` or `vitest`.
- - **Jest-compatible API:** `test`, `describe`, `expect`, `mock`, `spyOn`, `beforeAll`, `beforeEach`, `afterEach`,
- `afterAll`.
- - **Run with `bun test`** — auto-discovers `*.test.*` and `*.spec.*` files.
- - **Cleanup:** `mock.restore()` restores all spied functions, `mock.clearAllMocks()` clears history. Add to `afterEach`.
- - **`mock.module("./path", () => ({ ... }))`** for module mocking. Works for ESM and CJS.
+ - **Declare routes in the `routes` object** (1.2.3), and keep `fetch` for genuinely unmatched requests. Precedence is
+ exact, then parameterized, then wildcard, then the global catch-all — not source order.
+ - **Never deploy without either an `error` handler or an explicit `development: false`.** `development` defaults to
+ `process.env.NODE_ENV !== "production"`, so a server whose environment does not set `NODE_ENV=production` renders
+ Bun's contextual error page to any client that can trigger a throw. On 1.3.14 that page is a ~67 KB HTML overlay
+ carrying the thrown error's payload in an embedded `__bunfallback` script.
+ - **An `error` handler decides the response body in both modes** and replaces the error page when `development` is on.
+ Without one, `development: false` answers 500 with the body `Something went wrong!` (measured on 1.3.14). The stack
+ trace reaches stderr either way, so a missing handler costs you the response, never the log.
+ - **`idleTimeout` is 10 seconds and it covers a request whose handler has not yet written a byte.** A slow handler, a
+ long poll, or a server-sent-events stream is killed mid-response and the client sees a connection reset. Raise
+ `idleTimeout`, or call `server.timeout(req, 0)` for the one request that needs it.
+ - **Return a `Response` instance as the route value for anything fixed** — health checks, redirects, constant JSON. Bun
+ dispatches it with no allocation and caches it for the server's lifetime, so `server.reload()` is the only way to
+ change it.
+ - **`req.cookies` writes are applied to the response only under `routes`.** Inside a bare `fetch` handler nothing is
+ tracked and you build `Set-Cookie` yourself.
+ - **`server.upgrade(req)` must be followed by returning `undefined`**, never a `Response`.
+ - **Treat the return of `send()` and `publish()` as a status, not a byte count.** `-1` is backpressure and `0` is
+ dropped; from 1.4.0 `publish()` also returns `0` when the topic has no subscribers.
+ - **WebSocket handlers are declared once per server**, in the `websocket` object, not per socket. Type `ws.data` through
+ the handler object's `data` property.
+ - **`Response.error()` and any status outside 100–999 route to `error()`** and answer 500 (1.4.0). Earlier releases
+ wrote an invalid status line.
- Test modifiers, parametrized tests, mocking details, snapshots, CLI flags, and bunfig.toml test config: see
- `${CLAUDE_SKILL_DIR}/references/testing.md`.
+ Read [`${CLAUDE_SKILL_DIR}/references/http-server.md`] when serving files or a directory, wiring WebSocket pub/sub,
+ tuning server lifecycle or timeouts, or enabling HTTP/2 or HTTP/3 — it carries the five route value shapes and their
+ caching and `404` behavior, the directory-route path rules, the full `websocket` option set with its defaults, and the
+ `stop`, `reload`, and `closeIdleConnections` semantics.
- ## SQLite, Bundler, Plugins, Macros
+ ## Files
- - **SQLite:** use `bun:sqlite` — native, synchronous, 3-6x faster than `better-sqlite3`. Enable WAL mode. Use prepared
- statements and transactions.
- - **Bundler:** `Bun.build()` with targets `"bun"`, `"browser"`, `"node"`. Check `result.success` and iterate
- `result.logs` on failure.
- - **Plugins:** `Bun.plugin()` with `setup(build)` — extend module resolver and loader. Register via bunfig.toml
- `preload`.
- - **Macros:** compile-time code execution via `{ type: "macro" }` import. Return value inlined; must be
- JSON-serializable.
+ - **`Bun.file(path)` is lazy.** Constructing it touches no disk; `size` is `0` and `exists()` is `false` for a missing
+ path rather than throwing.
+ - **Read through the `Blob` interface** — `.text()`, `.json()`, `.bytes()`, `.arrayBuffer()`, `.stream()`.
+ - **`Bun.write(dest, data)` takes anything**: string, `Blob`, `BunFile`, `ArrayBuffer`, `TypedArray`, or `Response`. It
+ picks the fastest syscall for the pair, so copying a file is `Bun.write(Bun.file(dst), Bun.file(src))`, not a manual
+ read-then-write.
+ - **`file.writer()` returns a `FileSink` for incremental writes**, and the process stays alive until you call `.end()`.
+ `.unref()` opts out.
+ - **Use `node:fs` for directories.** `mkdir` and `readdir` have no `Bun.*` equivalent.
+ - **`Bun.stdin`, `Bun.stdout`, and `Bun.stderr` are `BunFile`s**, so streaming a file to stdout is one `Bun.write` call.
- Full SQLite API, bundler options, plugin patterns, and macro constraints: see
- `${CLAUDE_SKILL_DIR}/references/ecosystem.md`.
+ ## Shell and Processes
- ## Utilities
+ - **Interpolated values in `Bun.$` are single literal strings**, so command injection is closed by construction. Two
+ holes remain and neither is Bun's to close: spawning `bash -c "...${input}"` hands control to a real shell, and an
+ external command may read attacker-supplied text as its own flag (`--upload-pack=`). Validate arguments you pass on.
+ - **Only glob patterns written in the template expand** (1.4.0). A `*` arriving through `${...}`, a shell variable,
+ command substitution, or quoted text is literal, and `?`, `[...]`, and a leading `!` are literal everywhere.
+ - **A non-zero exit throws `ShellError`.** Use `.nothrow()` where you check `exitCode` yourself.
+ - **`Bun.$` is not `/bin/sh`.** It is an in-process interpreter, so `.sh` files run identically on Windows and the
+ builtin set is fixed.
+ - **`stderr` defaults to `"inherit"` in `Bun.spawn`, so `proc.stderr` is `undefined`.** Pass `stderr: "pipe"` to read
+ it. `stdout` defaults to `"pipe"` and `stdin` to `null`.
+ - **The parent process does not exit while a child is alive.** Call `proc.unref()` for a child that must not hold it
+ open.
+ - **Set `serialization: "json"` for IPC with a Node process.** The default `"advanced"` is JavaScriptCore's format and
+ Node cannot read it.
+ - **`Bun.spawnSync` for a CLI tool, `Bun.spawn` for a server.**
- ### Hashing & Passwords
+ Read [`${CLAUDE_SKILL_DIR}/references/shell-and-processes.md`] when redirecting shell I/O into JavaScript objects,
+ driving an interactive program through a PTY, applying cgroup limits, or spawning a `Worker` — it carries the
+ redirection and builtin tables, the stdio option matrix, the `Bun.Terminal` platform differences, and the worker
+ options.
- - `Bun.password.hash(pw)` — argon2id default. Also supports `"bcrypt"`.
- - `Bun.password.verify(pw, hash)` — auto-detects algorithm.
- - `Bun.hash("data")` — fast non-crypto (Wyhash).
- - `new Bun.CryptoHasher("sha256")` — crypto hashing.
+ ## Testing
- ### Sleep & Timing
+ - **Import from `bun:test`**, never from `jest` or `vitest`. `jest.*` and `vi.*` exist inside `bun:test` for ported
+ suites.
+ - **`mock.module()` is not hoisted.** Unlike `jest.mock`, it runs where it is written, so a module imported above it has
+ already been evaluated and its side effects have already happened. Put the override in a `--preload` script when the
+ original must never run.
+ - **Pick the right global reset.** `mock.clearAllMocks()` clears history and keeps implementations;
+ `jest.resetAllMocks()` also drops them (1.4.0 — before, it behaved like `clearAllMocks`); `mock.restore()` restores
+ spied originals and undoes no `mock.module()` override.
+ - **`expectTypeOf` is a no-op at runtime.** A green `bun test` proves nothing about types — run `bunx tsc --noEmit`
+ separately.
+ - **`bun test` sets `NODE_ENV=test` and `TZ=Etc/UTC`** unless either is already set. Do not re-set them in a preload.
+ - **An unhandled rejection between tests fails the run.** When it happens while a file loads, none of that file's tests
+ run at all.
+ - **Three concurrency knobs are independent**: `--parallel` spreads files across processes, `--concurrent` and
+ `test.concurrent` overlap async tests inside one file, `--shard=i/n` splits files across machines (all 1.3.13).
+ `--parallel` implies `--isolate`.
+ - **Reach for `--parallel --no-isolate` on a suite of many small files sharing a large import graph.** Isolation
+ re-evaluates every import per file, and on that shape plain `bun test` beats isolated workers.
+ - **Key per-worker resources off `BUN_TEST_WORKER_ID`** (or `JEST_WORKER_ID`) so parallel workers do not share a
+ database or a port.
+ - **The default per-test timeout is 5000 ms.**
- - `await Bun.sleep(ms)` — async. `Bun.sleepSync(ms)` — blocking.
- - `Bun.nanoseconds()` — high-resolution timer.
+ Read [`${CLAUDE_SKILL_DIR}/references/testing.md`] when writing the first test in a project, configuring CI sharding,
+ reaching for a matcher or a snapshot form, or debugging a mock that stopped returning values — it carries the discovery
+ patterns, the matcher inventory, the mock-reset semantics in full, the `--timings` sharding workflow, and the
+ `bunfig.toml` test keys.
- ### Comparison & Inspection
+ ## Package Manager
- - `Bun.deepEquals(a, b)` — deep equality. `Bun.deepMatch(subset, obj)` — partial match.
- - `Bun.inspect(obj)` — `console.log` format as string. `Bun.peek(promise)` — read without awaiting.
+ - **`trustedDependencies` replaces Bun's built-in allow list; it never extends it.** Adding one package silently
+ disables `postinstall` for every package the built-in list covered, so re-list the ones you still need. `[]` opts out
+ of lifecycle scripts entirely.
+ - **The built-in list applies only to npm-registry sources** (1.3.5). A `file:`, `link:`, `git:`, or `github:`
+ dependency named `esbuild` inherits nothing from the real `esbuild`.
+ - **The linker default comes from the lockfile's `configVersion`, not from a global setting.** A new monorepo gets the
+ isolated linker (1.3.2); a new single-package project and every project whose lockfile predates 1.3.2 get the hoisted
+ one. Two machines therefore lay out `node_modules` differently unless `bun.lock` is committed.
+ - **The isolated linker still permits phantom dependencies by default.** Set `[install] hoist = false` to stop
+ `node_modules/.bun/node_modules` from being created.
+ - **Run `bun ci` in CI, not `bun install`.** Bun does not turn on the frozen lockfile automatically; `bun ci` is
+ `bun install --frozen-lockfile`.
+ - **Commit `bun.lock`.** It is the text lockfile and the default from 1.2.
+ - **`--production` implies `--frozen-lockfile` and skips `devDependencies`, but removes nothing already installed.**
+ `bun prune --production` (1.4.0) is what shrinks a build image.
+ - **Use `bunx` to run a package binary** rather than installing it.
- ### Compression
+ Read [`${CLAUDE_SKILL_DIR}/references/package-manager.md`] when configuring a monorepo, choosing a linker, hardening a
+ supply chain, or diagnosing an install that differs between machines — it carries the `configVersion` table, the
+ `trustedDependencies` modes, workspaces and catalogs, the `--filter` grammar, `minimumReleaseAge` and the security
+ scanner, and the `bunfig.toml` install keys.
- - Gzip: `Bun.gzipSync(data)` / `Bun.gunzipSync(data)`.
- - Deflate: `Bun.deflateSync(data)` / `Bun.inflateSync(data)`.
- - Zstd: `Bun.zstdCompressSync(data)` / `Bun.zstdDecompressSync(data)`.
+ ## Building and Compiling
- ### Paths, UUIDs, Streams
+ - **`Bun.build()` rejects with an `AggregateError` on failure.** `throw` defaults to `true`, so `result.success` is
+ meaningful only when you pass `throw: false`. Checking `success` on a default build reads a value that never arrives.
+ - **`target` defaults to `"browser"`.** Set `target: "bun"` for server code, which also emits the `// @bun` pragma so
+ the runtime skips re-transpiling.
+ - **Ship `--compile --minify --sourcemap --bytecode` for a production binary.** Bytecode needs `target: "bun"`, and for
+ ES modules it needs `--compile` as well (1.3.9).
+ - **A compiled binary does not auto-load `tsconfig.json` or `package.json`** (1.3.4), but it does auto-load `.env` and
+ `bunfig.toml`. Pass `--no-compile-autoload-dotenv` and `--no-compile-autoload-bunfig` where a deployment must not read
+ files from its working directory.
+ - **Cross-compile with `--target bun-<os>-<arch>[-musl]`.** From 1.4.0 x64 ships one binary that selects AVX paths at
+ runtime, so there is no `-baseline` or `-modern` choice to make.
+ - **A macro's return value must be serializable and its arguments statically known.** Code inside `node_modules` cannot
+ invoke a macro, though your own code may import one from a package.
+ - **`Bun.markdown.html()` does not sanitize.** Raw HTML, event-handler attributes, and `javascript:` hrefs pass through
+ verbatim, so untrusted Markdown needs a sanitizer before it reaches a browser.
- - `Bun.randomUUIDv7()` — time-ordered. `crypto.randomUUID()` — standard v4.
- - Stream helpers: `Bun.readableStreamToText/JSON/Bytes/Blob/Array/ArrayBuffer(stream)`.
- - `Bun.escapeHTML("<script>")`, `Bun.stringWidth("hello")`.
+ Read [`${CLAUDE_SKILL_DIR}/references/build-and-compile.md`] when configuring a bundle, embedding assets in an
+ executable, writing a plugin, or reaching for a macro — it carries the full `Bun.build` option set, code-splitting and
+ `optimizeImports` behavior, the cross-compilation targets, the embedded-file and `/$bunfs` rules, the plugin hooks, and
+ the macro serialization constraints.
- ### Environment & Metadata
+ ## Data Clients
- - `Bun.version`, `Bun.revision` (git hash), `Bun.env` (alias for `process.env`), `Bun.main` (entrypoint path).
- - `import.meta.dir`, `import.meta.file`, `import.meta.path` — current file info.
+ - **Open `bun:sqlite` with `{ strict: true }`.** Without it a missing bind parameter is not an error — a typo binds
+ nothing and the query returns a wrong result silently.
+ - **`db.query()` caches the compiled statement (20 most recent); `db.prepare()` does not.** Use `prepare()` for SQL
+ generated at runtime so one-off queries do not evict the statements that repeat.
+ - **Enable WAL** with `PRAGMA journal_mode = WAL` for anything with concurrent readers.
+ - **Set `safeIntegers: true` when a column can exceed 2^53.** The default returns `number` and rounds silently.
+ - **Every interpolation in a `Bun.sql` template is a parameter.** Use the `sql()` helper for identifiers, column
+ subsets, and value lists; `sql.unsafe` escapes nothing.
+ - **`Bun.sql` queries are lazy** — they start on `await` or `.execute()`, and `.cancel()` stops a running one.
+ - **Release a reserved connection.** `sql.reserve()` takes one out of the pool until `release()`, or use `using`.
+ - **Set `prepare: false` behind PgBouncer in transaction mode.**
- ### HTMLRewriter
+ Read [`${CLAUDE_SKILL_DIR}/references/data-clients.md`] when choosing a client, closing a SQLite database cleanly,
+ building dynamic SQL, or configuring pooling and TLS — it carries the `bun:sqlite` close and WAL-sidecar semantics, the
+ `Bun.sql` fragment helpers and query modes, and the `Bun.redis` and `Bun.S3Client` configuration.
- - Cloudflare-compatible HTML streaming transformer. Works on `Response` objects and strings.
+ ## Configuration and Environment
- ## Package Manager — `bun install`
+ - **`bunfig.toml` configures the runtime, `bun test`, `bun install`, and `bun run`.** A CLI flag overrides it; a
+ project's `bunfig.toml` overrides `.npmrc` for the same key (1.4.0).
+ - **`.env` files load automatically**, in order: `.env`, then `.env.{development,production,test}` by `NODE_ENV`, then
+ `.env.local` (skipped when `NODE_ENV=test`), then `.env.{mode}.local`. Values expand `$VAR` references unless the `$`
+ is escaped.
+ - **Bun invoked as `node` does not load `.env`** (1.4.0) — under `bun --bun`, `bunx --bun`, or a `node` symlink. A
+ `package.json` script calling `node` under `bun --bun run` therefore sees those variables as `undefined`. Pass
+ `--env-file` to keep them.
+ - **Disable automatic loading in production with `--no-env-file` or `env = false`.** Files named by `--env-file` still
+ load.
+ - **`bunfig.toml` parsing is strict** (1.4.0): an unquoted string value, a missing newline between pairs, or an integer
+ past `Number.MAX_SAFE_INTEGER` is a `SyntaxError` at startup.
+ - **`Bun.YAML` follows YAML 1.2** (1.3.5), so `yes`, `no`, `on`, and `off` parse as strings. An `on:` key in a GitHub
+ Actions workflow is the string `"on"`.
- Drop-in replacement for npm/yarn/pnpm. ~25x faster.
+ ## Node Compatibility
- - **Lockfile:** `bun.lock` (text, default since v1.2) or `bun.lockb` (binary).
- - **Does NOT run `postinstall`** of dependencies by default (security). Add to `trustedDependencies` in `package.json`
- to allow.
- - **Workspaces** supported via `package.json` `workspaces` field.
- - **Auto-install:** when no `node_modules` found, Bun resolves packages on the fly.
- - **`bunx`:** execute package binaries without installing (like `npx`).
- - **`bun install --production`** skips devDependencies.
+ Bun targets Node 26 and most packages run unchanged. Reach for a `node:` module deliberately in these cases:
- ## Configuration & Compatibility
+ - **Directory operations** — `node:fs`. No Bun equivalent exists.
+ - **`node:sea`** — not implemented. `bun build --compile` is the replacement and a different API.
+ - **`node:test`** — partial. Write `bun:test`.
+ - **`node:crypto` beyond BoringSSL** — no `ed448`, `x448`, `rsa-pss`, `dsa`, `dh`, `secp256k1`, or the CCM, OCB, XTS,
+ and `chacha20-poly1305` ciphers.
+ - **`node:async_hooks`** — `AsyncLocalStorage` works but does not propagate into `Worker`, `MessagePort`, or
+ `BroadcastChannel`. `createHook` and the async-id APIs are stubs.
+ - **HTTP load balancing across processes** — `node:cluster` shares an HTTP socket only on Linux, through `SO_REUSEPORT`.
+ - **`node:v8` `serialize`/`deserialize`** — JavaScriptCore's wire format, not V8's, so a buffer does not travel between
+ Bun and Node.
+ - **Loader hooks** — `module.register` is a no-op. Use `Bun.plugin`.
- bunfig.toml sections, environment variable loading, and Node.js API compatibility details: see
- `${CLAUDE_SKILL_DIR}/references/config-and-compat.md`.
+ Read [`${CLAUDE_SKILL_DIR}/references/node-compat.md`] when porting a Node codebase, when a `node:` API behaves
+ differently under Bun, or when a package that works under Node fails — it carries the per-module gap list, the Node 26
+ changes that landed in 1.4.0, and the `fetch`, Web API, and module-resolution behavior changes that break working code.
## Application
- When **writing** Bun code:
-
- - Apply all conventions silently — don't narrate each rule being followed.
- - If an existing codebase uses Node.js patterns, follow codebase style but flag that Bun-native alternatives exist.
- - For new projects, use Bun-native APIs throughout.
+ When **writing** Bun, apply these conventions silently — do not narrate a rule while following it. Where existing code
+ uses a `node:` API that has a Bun-native equivalent, follow the codebase and flag the alternative once.
- When **reviewing** Bun code:
+ When **reviewing** Bun, cite the violation and show the fix inline. Do not lecture.
- - Cite the specific Node.js-to-Bun migration and show the fix inline.
- - Don't lecture — state what's suboptimal and how to fix it.
+ ```
+ Bad: "Bun's build API generally prefers rejection over a status flag, so you may want to..."
+ Good: if (!result.success) -> Bun.build() rejects; catch the AggregateError, or pass throw: false
+ ```
## Integration
- The **javascript** skill governs language choices; this skill governs Bun runtime and toolchain decisions. Activate
- **typescript** alongside both when working with TypeScript.
+ The **javascript** and **typescript** skills own the language and the type system; this skill states only where Bun
+ departs from them. The **nodejs** skill owns what a `node:` module does — this skill owns only where Bun's
+ implementation is absent, partial, or behaves differently, and never teaches Node itself.
- **Use Bun APIs, not Node.js polyfills. When in doubt, check if Bun has a native API.**
+ `bun:test` and `bun install` are this skill's, and the **vitest** skill owns Vitest. Which of them a project adopts is a
+ project decision, not a rule either skill states. The **coding** skill governs workflow.
+
+ **When in doubt, check whether Bun already ships it.**