CLAUDE.md
src/CLAUDE.mdCLAUDE.md
Quality
76/100
Scores the file, not the repository.Length
1,774 words
17 headings · 17 code blocksRepository
95k
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.1## Rust23`src/` is a Cargo workspace (rooted at the repo's top-level `Cargo.toml`, ~2004member crates). The runtime is built as `libbun_rust.a` via `cargo build -p5bun_bin` (driven by `scripts/build/rust.ts`). Key crates:67- `bun_core` (`src/bun_core/`) — strings, formatting, logging, env vars, allocator/heap helpers, the foundation everything else uses8- `bun_sys` (`src/sys/`) — cross-platform syscall wrappers (`File`, `Fd`, `Dir`, `Error`)9- `bun_paths` (`src/paths/`) — path joining/normalization, the path-buffer pool10- `bun_jsc` (`src/jsc/`) — JSC value types, `Strong`/`Weak`, FFI imports, `URL`11- `bun_runtime` (`src/runtime/`) — JS-visible APIs (server, fetch, node compat, crypto)12- `bun_js_parser`, `bun_js_printer`, `bun_resolver`, `bun_bundler`, `bun_install`, `bun_collections`, `bun_threading`, `bun_alloc` — the rest of the pipeline13- `bun_bin` (`src/bun_bin/`) — the staticlib root that `cargo build` links1415Conventions:1617- `cargo check -p <crate>` for fast iteration; `bun bd` builds and links everything.18- Don't `.unwrap()` a fallible path that user input or the OS can hit at runtime — return the error. `.unwrap()` is for invariants you can prove.19- The C ABI / syscall boundary uses `bun_sys::Maybe<T>` (= `Result<T, bun_sys::Error>`); ordinary Rust code uses `Result<T, E>` with `?`.20- Each crate defines its own `Error` enum (a `thiserror::Error` at `<crate>/error.rs`, re-exported as `crate::Error` + `crate::Result`). Errno codes nest via `Sys(#[from] bun_errno::SystemErrno)`; OOM via `Alloc(#[from] bun_alloc::AllocError)`. `bun_sys::Error` is the rich syscall error (errno + syscall tag + path); `From<bun_sys::Error> for bun_errno::SystemErrno` exists for `?`-chaining.21- NEVER add comments to deleted code blocks.22- Do not add comments that reference context from the transcript.23- Avoid adding comments where not necessary.2425## Prefer `bun_core` / `bun_sys` over `std`2627The `std` equivalents either lose OS error info, allocate where we have pools,28or don't match the cross-platform behavior the runtime needs.2930| Instead of | Use |31| --------------------------------------- | ------------------------------------------------------------------------------------ |32| `std::fs::File` | `bun_sys::File` (owns the fd; closes on `Drop`) |33| `std::fs::read` / `write` | `bun_sys::File::read_from` / `File::create` + `write_all` |34| `std::path::Path::join` | `bun_paths::resolve_path::join` / `join_string_buf` |35| `std::path::Path::parent`/`file_name` | `bun_paths::dirname` / `bun_paths::basename` |36| `std::env::var` | `bun_core::env_var::*::get()` (typed + cached) |37| `String::from_utf8` for JS-visible strs | `bun_core::String::clone_utf8` / `borrow_utf8` |38| `&str` operations on byte slices | `bun_core::strings::*` (SIMD-backed `&[u8]` ops) |39| `eprintln!` for debug logging | `bun_core::declare_scope!` + `scoped_log!` |40| `std::process::Command` | `bun_core::util::spawn_sync_inherit` (CLI helpers) or `bun_spawn_sys` (full control) |41| `Box::new` + raw ptr round-trip | `bun_core::heap::{into_raw, take, destroy}` |4243## `bun_sys` — System Calls (`src/sys/`)4445Syscall wrappers preserve errno via `Maybe<T> = Result<T, bun_sys::Error>`.4647```rust48use bun_sys::{File, Fd, O};4950let file = File::openat(Fd::cwd(), b"path/to/file", O::RDONLY, 0)?;51let mut buf = vec![0u8; 4096];52let n = file.read_all(&mut buf)?; // loops until EOF or full53// `file` closes on Drop.54```5556Key types and functions:5758- `Fd` (`bun_core::Fd`, re-exported) — cross-platform file descriptor. `Fd::cwd()`, `Fd::stdin()/stdout()/stderr()`, `fd.close()`.59- `File::open(path: &ZStr, flags, mode)` / `File::openat(dir: Fd, path: &[u8], flags, mode)` / `File::make_open(...)` (creates parent dirs) / `File::create(dir, path, truncate)`60- `file.read(buf)` / `read_all(buf)` / `read_to_end()` / `read_to_end_small()` / `write(buf)` / `write_all(buf)`61- `bun_sys::open`, `read`, `write`, `pread`, `pwrite`, `stat`, `fstat`, `lstat`, `mkdir`, `unlink`, `rename`, `symlink`, `chmod` — free fns over `Fd`62- Open flags: `bun_sys::O::RDONLY`, `O::WRONLY | O::CREAT | O::TRUNC`, etc.6364`bun_sys::Error` carries `errno`, `syscall: Tag`, `path: Box<[u8]>`. Convert65to a JS exception via `bun_sys_jsc::ErrorJsc::to_js`:6667```rust68use bun_sys_jsc::ErrorJsc;69match File::openat(Fd::cwd(), path, O::RDONLY, 0) {70 Ok(f) => f,71 Err(err) => return Ok(err.to_js(global)?),72}73// Internally: err.to_system_error().to_error_instance(global)74```7576## Strings (`bun_core::String` and `bun_core::strings`)7778`bun_core::String` is the FFI-compatible 5-variant tagged union shared with C++79(`BunString` in `BunString.cpp`). It bridges Rust and JSC and can hold a80`WTFStringImpl` (Latin-1 or UTF-16). **Latin-1 is NOT UTF-8** — bytes 128–25581are single chars in Latin-1 but invalid UTF-8 — so converting either direction82requires a real encoder, not a cast.8384```rust85use bun_core::String;8687let s = String::clone_utf8(utf8_bytes); // copies into a WTFStringImpl88let s = String::borrow_utf8(utf8_bytes); // no copy; caller keeps slice alive89let s = String::static_(b"literal"); // 'static slice, never freed9091let utf8: ZigStringSlice = s.to_utf8(); // ref-holding view; falls back to allocating a copy92let owned: Vec<u8> = s.to_utf8_bytes();93```9495To/from JS values, use the `bun_jsc::StringJsc` extension trait:9697```rust98use bun_jsc::StringJsc;99let js: JSValue = s.to_js(global)?;100let s = bun_core::String::from_js(value, global)?;101let err = s.to_error_instance(global);102```103104`bun_core::strings` is the SIMD-backed `&[u8]` toolkit. Use it instead of105`std::str` / `std::iter` for searching and comparing byte slices:106107```rust108use bun_core::strings;109110strings::index_of(haystack, needle) // Option<usize>111strings::contains(haystack, needle) // bool112strings::eql(a, b) // bool113strings::starts_with(s, prefix) // bool114strings::ends_with(s, suffix) // bool115strings::has_prefix_comptime(s, b"x") // 'static comparand116strings::has_suffix_comptime(s, b"x")117strings::first_non_ascii(s) // Option<u32>118strings::to_utf16_alloc(...) // encoding conversions119```120121## Paths (`bun_paths`)122123Path helpers operate on `&[u8]` and are platform-parameterized via the124`Platform` const-generic (`Posix`, `Windows`, `Loose`, `Nt`; `platform::Auto`125picks the host). Never use `std::path` for runtime path logic.126127```rust128use bun_paths::{dirname, basename};129use bun_paths::resolve_path::{self, platform};130131let dir = dirname(path); // Option<&[u8]>132let name = basename(path); // &[u8]133let joined = resolve_path::join::<platform::Auto>(&[a, b]); // &'static [u8] (threadlocal buf)134let joined = resolve_path::join_string_buf::<platform::Auto>(&mut buf, &[a, b]); // caller buf135let rel = resolve_path::relative(from, to);136```137138Use the path-buffer pool to avoid 64 KB stack allocations on Windows139(`PathBuffer` is `[u8; PATH_MAX_BYTES]`, ~64 KB on Windows):140141```rust142use bun_paths::path_buffer_pool;143144let mut buf = path_buffer_pool::get(); // PoolGuard<PathBuffer>, returns to pool on Drop145let joined = resolve_path::join_string_buf::<platform::Auto>(&mut *buf, &[a, b]);146```147148`bun_paths::os_path_buffer_pool` selects the wide (`u16`) variant on Windows149and the narrow (`u8`) variant on POSIX.150151## URL Parsing (`bun_jsc::URL`)152153WHATWG-compliant, backed by WebKit's URL parser. Returns `None` for invalid input.154155```rust156use bun_jsc::URL;157158let url = URL::from_utf8(href)?; // Option<NonNull<URL>>159// caller owns the C++ object — destroy it when done:160// unsafe { URL::destroy(url.as_ptr()) }161162url.protocol() // bun_core::String163url.pathname() // bun_core::String164url.host() // bun_core::String — the hostname WITHOUT the port (opposite of JS `host`!)165url.port() // u32 (u32::MAX = unset; otherwise u16 range)166```167168`URL::href_from_js`, `URL::file_url_from_string`, `URL::path_from_file_url`169do whole-string conversions. The JSC-free shim `bun_url::whatwg::URL` exposes170`hostname()`, which returns the host WITH the port (also the opposite of JS171`hostname`) — so `bun_jsc::URL::host` and `bun_url::whatwg::URL::hostname`172are effectively swapped relative to their JS namesakes.173174## MIME Types (`bun_http_types::MimeType`)175176```rust177use bun_http_types::{MimeType, mime_type};178179let mime = mime_type::by_extension(b"html"); // MimeType180let mime = mime_type::by_extension_no_default(b"xyz"); // Option<MimeType>181182mime.category // Category::Javascript | Css | Html | Json | Image | Text | Wasm | ...183mime.category.is_text_like()184```185186Common constants: `JAVASCRIPT`, `JSON`, `HTML`, `CSS`, `TEXT`, `WASM`, `ICO`, `OTHER`.187188## Memory & Allocators189190The `#[global_allocator]` is mimalloc (or `std::alloc::System` under191`cfg(bun_asan)`), so plain `Box`/`Vec`/`String` already use it. When pairing192with C/C++ that may free the bytes, route through `bun_alloc::default_alloc`193rather than `mi_*` directly — under ASAN the global allocator is libc's, so a194`mi_free`/`mi_usable_size` on `Box`-owned memory is an allocator mismatch.195196OOM handling: do not let a runtime OOM unwind into FFI. Use197`bun_core::handle_oom` (or the `.unwrap_or_oom()` extension) to convert198`Result<T, AllocError>` into a controlled crash:199200```rust201use bun_core::{handle_oom, UnwrapOrOom};202let buf = handle_oom(allocator.alloc(size));203let v = vec.try_reserve(n).unwrap_or_oom();204```205206Heap round-trips that need to cross FFI use `bun_core::heap`:207208```rust209use bun_core::heap;210let raw: *mut T = heap::into_raw(Box::new(value)); // hand ownership to C211let boxed: Box<T> = unsafe { heap::take(raw) }; // reclaim ownership212unsafe { heap::destroy(raw) }; // reclaim + drop in one step213```214215**Arena gotcha:** values allocated in `bun_alloc::MimallocArena` (the AST216allocator and similar) do **not** run `Drop` when the arena resets — the217backing pages are bulk-freed. If a type owns a heap allocation, refcount, or218fd, free it explicitly before the arena resets. Don't rely on `Drop` for219correctness in arena-backed code.220221## Environment Variables (`bun_core::env_var`)222223Typed, cached accessors. Each known env var is a module with a `get()`224returning the right type (`Option<...>` if no default).225226```rust227use bun_core::env_var;228229env_var::HOME::get() // Option<&[u8]>230env_var::CI::get() // bool (has default)231env_var::BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS::get() // u64 (has default)232```233234## Logging (`bun_core::output`)235236Scoped debug logging. Declare a scope once per module; gate with237`BUN_DEBUG_<SCOPE>=1` at runtime; the body dead-strips in release builds.238239```rust240bun_core::declare_scope!(my_feature, hidden); // hidden: opt-in via BUN_DEBUG_my_feature=1241// or `visible` to log by default in debug builds242243bun_core::scoped_log!(my_feature, "processing {} items", count);244```245246User-facing colored output (auto-detects TTY, strips ANSI when piped):247248```rust249bun_core::pretty!("<green>success<r>: {}\n", msg);250bun_core::prettyln!("done");251bun_core::pretty_errorln!("<red>error<r>: {}", msg);252```253254## Spawning Subprocesses255256For simple inherit-stdio CLI helpers:257258```rust259use bun_core::util::spawn_sync_inherit;260let status = spawn_sync_inherit(&[b"git", b"status"])?;261```262263For full control (pipes, custom env, posix_spawn flags) use `bun_spawn_sys`264(`src/spawn_sys/`). The runtime `Bun.spawn` implementation lives in265`src/runtime/api/bun/{spawn.rs, process.rs, subprocess.rs}` — look there for266the JS-facing path.267268## JSC Interop & FFI Safety269270These are the patterns that trip people up. Get them wrong and you get271crashes that only reproduce under load or in CI.272273### Pointer provenance at FFI boundaries274275If a callback may free `self` (close, error, GC finalize), do **not**276materialize `&self`/`&mut self` at the boundary — a `&self`-derived raw277pointer carries `SharedReadOnly` provenance, and `Box::from_raw`/dealloc278through it is UB. Pass and dispatch off `*mut Self` until the body proves279ownership. `src/io/PipeWriter.rs`'s `impl_streaming_writer_parent!` macro280encodes the three modes:281282- `borrow = mut` — body forms `&mut *this`; safe when nothing re-enters283- `borrow = shared` — body forms `&*this`; safe when re-entrant code only needs `&Self`284- `borrow = ptr` — body calls `Self::method(this, ..)` with `this: *mut Self`; required when the callback may free `self`285286### `Strong` / `Weak` JS handles287288`bun_jsc::Strong` keeps a JS value alive; it is `!Send`/`!Sync` and must be289created and dropped on the JS thread.290291```rust292use bun_jsc::Strong;293let strong = Strong::create(value, global);294let v: JSValue = strong.get();295// drop(strong) releases the GC handle296```297298`bun_jsc::Weak<T>` is the GC-cleared variant. For raw values without a `Strong`299wrapper, `JSValue::protect()` / `unprotect()` and `ensure_still_alive()` are300available, but `Strong` is preferred — it can't be forgotten or unbalanced.301302### Refcount transfer on `to_js()` / `create()`303304A `to_js()` / `create()` that returns a wrapped pointer **transfers** the305caller's `+1` to the JS wrapper. Do not `ref()` again before the return; the306finalizer derefs once. The leak-or-UAF symptoms of getting this wrong are307distinctive: an extra `ref()` leaks until process exit; a missing `ref()` on a308non-transferring path UAFs at GC.309310### Cross-thread string hazards311312`AtomString`s live in a per-thread table. Never deref one from another thread —313it trips `wasRemoved` in `AtomStringImpl::remove()`. If a `bun_core::String`314may be dropped from a non-JS thread (HTTP worker, threadpool, dying VM), build315it via `String::clone_utf8` (a plain `WTFStringImpl` with an atomic refcount),316not from an interned/atomized JS string. See the comment in317`src/runtime/webcore/fetch/FetchTasklet.rs` near `Response::init` for the318canonical example of this bug class and its fix.319320## Common Patterns321322```rust323// Read a file, return JS error on failure324let contents = match bun_sys::File::openat(Fd::cwd(), path, O::RDONLY, 0)325 .and_then(|f| f.read_to_end())326{327 Ok(bytes) => bytes,328 Err(err) => return Ok(err.to_js(global)?),329};330331// Heap-allocated FFI handle with explicit lifecycle332let raw = bun_core::heap::into_raw(Box::new(MyHandle::new()));333register_with_c(raw);334// ... later, in the matching teardown callback:335unsafe { bun_core::heap::destroy(raw) };336337// Hashing338bun_wyhash::hash(bytes) // u64339bun_wyhash::hash_with_seed(seed, bytes)340```341
Also in oven-sh/bun
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| oven-sh/bun.github/workflows/CLAUDE.md · 95k | CLAUDE.md | testlint-formatarchgit+2 | 81/100 | today | |
| oven-sh/bunCLAUDE.md · 95k | CLAUDE.md | buildteststylearch+3 | 96/100 | 3 days ago | |
| oven-sh/bunscripts/verify-baseline-static/CLAUDE.md · 95k | CLAUDE.md | buildtesting-strategy | 65/100 | 3 days ago | |
| oven-sh/bunsrc/js/CLAUDE.md · 95k | CLAUDE.md | buildarchdo-not | 85/100 | 3 days ago | |
| oven-sh/bunsrc/jsc/bindings/v8/AGENTS.md · 95k | AGENTS.md | buildtestarchtesting-strategy+4 | 81/100 | 3 days ago | |
| oven-sh/bunsrc/jsc/bindings/v8/CLAUDE.md · 95k | CLAUDE.md | buildtestarchtesting-strategy+4 | 81/100 | 3 days ago | |
| oven-sh/buntest/CLAUDE.md · 95k | CLAUDE.md | teststyletesting-strategydo-not | 97/100 | 3 days ago | |
| oven-sh/buntest/js/node/test/parallel/CLAUDE.md · 95k | CLAUDE.md | test | 43/100 | 3 days ago |
Diff against .github/workflows/CLAUDE.md Diff against CLAUDE.md Diff against scripts/verify-baseline-static/CLAUDE.md Diff against src/js/CLAUDE.md Diff against src/jsc/bindings/v8/AGENTS.md Diff against src/jsc/bindings/v8/CLAUDE.md Diff against test/CLAUDE.md Diff against test/js/node/test/parallel/CLAUDE.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| bagisto/bagistoCLAUDE.md · 28k | CLAUDE.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| livewire/livewireCLAUDE.md · 24k | CLAUDE.md | setupbuildteststyle+4 | 100/100 | 3 days ago | |
| microsoft/playwrightCLAUDE.md · 94k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 3 days ago | |
| nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.4k | CLAUDE.md | setupbuildstylearch+2 | 100/100 | 3 days ago | |
| filamentphp/filamentCLAUDE.md · 32k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| Adit-Jain-srm/NightmareNetCLAUDE.md · 45 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 3 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today |
