Skip to main content

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 data clients, `bun:test`, the bundler and compiled binaries, and the package manager.

Source facts

Repository
xobotyi/cc-foundry
Last source activity
October 13, 2026 at 09:31
Detected SKILL.md language
English
Stars
22
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
9 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
bun
description
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 data clients, `bun:test`, the bundler and compiled binaries, and the package manager.
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's value is that the runtime, the toolchain, and the batteries are one binary. Two biases decide most calls: - **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. ## Release Version 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". The rules below assume 1.3.0 or later unless a version anchor says otherwise. Floors for the features these rules reference: - **1.3.0** — `Bun.sql` for MySQL, MariaDB, and SQLite; `Bun.redis`; `Bun.secrets`; `Bun.YAML`; catalogs; `test.concurrent`, `test.serial`, and `--concurrent`; `--randomize`; `expectTypeOf` - **1.3.2** — the isolated linker as the default for new monorepos; `nativeDependencies`; `ignoreScripts` - **1.3.3** — `test(..., { retry })` - **1.3.4** — `jest.useFakeTimers()`; `URLPattern`; `--compile` stops auto-loading `tsconfig.json` and `package.json` - **1.3.5** — `Bun.Terminal`; `Bun.stringWidth`; `bun:bundle` feature flags; `trustedDependencies` limited to npm sources - **1.3.6** — `Bun.JSONC`; `Bun.Archive`; `Bun.build({ files })`; `metafile: true` - **1.3.7** — `Bun.JSON5`; `Bun.JSONL`; `Bun.wrapAnsi` - **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()`; `Bun.sliceAnsi` - **1.3.12** — `Bun.WebView` - **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; `Bun.XML`; `Bun.isStandaloneExecutable`; `Bun.spawn({ cgroup })`; directory routes; `bun audit fix`, `bun dedupe`, `bun prune`, `bun pm diff`, `bun pm licenses`; `bun test --timings`; `--asset`; `lockfileVersion: 2` ## Prefer the Built-in Reach for the built-in, and drop the package it replaces from `package.json`. - **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` (1.3.7), `Bun.JSONC` (1.3.6), `Bun.XML` (1.4.0) over `@iarna/toml`, `js-yaml`, `json5`, `jsonc-parser`, and `fast-xml-parser` - **Newline-delimited JSON** — `Bun.JSONL` over `ndjson` (1.3.7) - **Tarballs** — `Bun.Archive` over `tar` (1.3.6) - **Terminal string width** — `Bun.stringWidth` (1.3.5), `Bun.wrapAnsi` (1.3.7), `Bun.sliceAnsi` (1.3.11) over `string-width`, `wrap-ansi`, `slice-ansi`, and `cli-truncate` - **Route patterns** — `URLPattern` over `path-to-regexp` (1.3.4) - **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` 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. ## HTTP Server - **Declare routes in the `routes` object**, 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. 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. ## Files - **`Bun.file(path)` is lazy.** Constructing it touches no disk; `size` is `0` and `exists()` is `false` for a missing path rather than throwing. - **`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 `.end()` is called. `.unref()` opts out. - **`Bun.stdin`, `Bun.stdout`, and `Bun.stderr` are `BunFile`s**, so streaming a file to stdout is one `Bun.write` call. ## Shell and Processes - **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. 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. ## Testing - **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. - **Pass the dependency in before reaching for `mock.module()`.** A fake passed as a parameter or a constructor argument replaces one collaborator for one test. A module override reaches every importer, even one that imported the module before the override, and `mock.restore()` does not undo it. - **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 the project's type-check command separately. - **`bun test` sets `NODE_ENV=test` unless it is already set, and runs in UTC.** `process.env.TZ` stays unset — the runtime zone is UTC, not an environment variable a preload can read. - **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 (`--parallel` and `--shard` from 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.** 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. ## Package Manager - **`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.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub