Skip to content

Resolve the audited TODO(port)/TODO(b2)/PORT NOTE backlog#31783

Merged
Jarred-Sumner merged 8 commits into
mainfrom
claude/todo-audit-fixes
Jun 5, 2026
Merged

Resolve the audited TODO(port)/TODO(b2)/PORT NOTE backlog#31783
Jarred-Sumner merged 8 commits into
mainfrom
claude/todo-audit-fixes

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Resolve the audited TODO(port)/TODO(b2)/PORT NOTE backlog

What this PR does

The Rust port accumulated a large backlog of TODO(port) / TODO(b2-*) markers and
"PORT NOTE" comments — a mix of stale organizational notes, completed-but-unverified
debts, real small bugs, and genuinely hard lifetime/GC restructures. This PR drains
that backlog: every one of the 2,327 audited markers (plus 1,342 PORT NOTE comment
lines across 346 files) was individually resolved — fixed, verified-and-documented,
deleted as stale, or split into a tracked follow-up work order.

Net diff: 1,025 files, +13,985 / −14,705.

Method

Audit-driven, per-file passes: each group of files got a fix pass, two independent
adversarial reviews, and a repair pass addressing reviewer findings; a second phase
drained cross-file escalations and leftover markers the same way.

Summary of actions

Action Count
Stale/organizational TODO removed (content verified first) ~1,240
Load-bearing comment kept, untagged to plain/SAFETY/PERF comment ~780 tagged comments + 1,342 PORT NOTE lines
Real code fix or completed verification debt ~495
Pure bookkeeping comment lines deleted ~280
Split into a tracked follow-up work order (marker kept) 101 markers → 43 work-order files
Marker described already-landed work 18
Covered by an open PR; left alone 6
Fix reverted after review (then redone correctly later) 1

Notable code fixes by subsystem

  • install: lockfile migration cursor pointers replaced with safe pushes; SaveTask
    no longer borrows registry Scope across the threadpool; pre-hashed maps switched to
    identity hashing; detached-parent lifecycle-script completion uses the atomic
    increment the Zig source specified; dead ScriptRunEnvironment removed.
  • jsc bindings: restored dropped debug asserts (JSCell getter-setter types, WTF-8
    printer validation); Strong::get reads the handle slot like the Zig original;
    member-binding tracking wired into JSCell accessors; hot-reload error traces wired.
  • runtime: test.each arguments are now GC-rooted for the duration of label
    formatting/binding; html_rewriter end-tag callbacks use RAII protection; missing
    globalThis.wasmSourceBytes wiring for compiled-wasm entrypoints; server request
    error logging prints the JS stack trace; stream byte-blob fast path uses an
    uninitialized-then-copied Uint8Array like upstream.
  • cli: bun init full-stack template runs bun install with stdin ignored
    (matching upstream behavior) via a new no-stdin spawn helper with per-platform
    handling; open_url actually spawns the opener; missing-entrypoint fallback to help
    restored; completions install command delegates to the full implementation.
  • js_printer / js_parser / ast: template-literal folding is written back so
    minified output uses it; identifier-range lookup is a faithful full-Unicode port;
    per-call heap allocation removed from the printer's fmt path; macro enablement
    matches the wasm gate.
  • css: border-image fallbacks moved instead of deep-cloned; font handler uses
    single-lookup map entries.
  • bundler/resolver/sourcemap: entry-point offset for server components restored;
    renamer teardown matches upstream defer; several 'static lifetime erasures replaced
    with real borrows or owned copies.
  • crash handler / analytics: feature counters (todo_panic, unsupported_uv_function)
    and telemetry-enabled checks now match the spec.
  • collections: release-mode panic on >256-byte keys in the comptime string map
    fixed with a heap fallback; dead APIs removed; Zig-PRNG-faithful tests ported.
  • windows: Win32 error codes map through a real errno-name hook instead of
    collapsing to "Unexpected"; long-path error split (FileNotFound vs NameTooLong).
  • Plus a long tail of dead-code deletion, error-type narrowing, and
    verification-debt closure — full per-entry list in the group reports.

What deliberately remains

101 TODO(...) markers remain in-tree, each claimed by a written work order.
These are the changes that should not land blind in a bulk branch: GC rooting of bare
JSValue fields, crate-wide arena-lifetime threading in bun_css, cross-thread
ownership restructures (installer task queue, copy_file, dns caching backrefs),
Stacked-Borrows provenance hardening, Windows fd-ownership modeling, and a ~1,400-line
diff test-suite port. Each gets a dedicated, individually reviewable follow-up PR.

Zero "PORT NOTE" strings remain. A follow-up sweep on this branch also removes all
remaining Zig references from comments (~18k lines: file/line cross-references,
std.* stdlib mentions, quoted Zig syntax, divergence notes) — this codebase is a
standalone Rust implementation and its comments now read that way. SAFETY
justifications were rewritten in terms of the Rust code's own invariants rather
than deleted. References to current identifiers (ZigString etc.) are unchanged.

Verification

  • Full debug build (linux-x64, ASAN) compiles and links clean. Three issues were
    caught and fixed by the compile pass: a unit-struct FFI-alignment marker in
    uws_sys rewritten to the zero-length-array idiom, missing parens in two
    debug_assert!s, and a CompileResultSlots::get_mut accessor added so the
    DevServer HTML ownership transfer borrows mutably without new unsafe.
  • Smoke-tested: eval, timers, Bun.$ shell, bun test. The setTimeout suite's
    3 leak-threshold failures reproduce identically on main's debug build
    (pre-existing, not from this branch).
  • Cross-target cargo check (windows/macos/linux × x64/aarch64) run before push.
  • Two CI failures found and fixed on this branch: parsePackedFeaturesList()
    updated for the reshaped define_features! macro, and a hot-reload server
    registration bug — NewServer::init moves the config into the server, so the
    hot-map insert after init read an empty id and panicked on the second
    Bun.serve under --hot. The path was unreachable before this branch's
    hot_map() lazy-init fix exposed it; registration now reads the server's own
    config, and HTTPServerAgent.test.ts passes.

Caveats worth knowing during review: a handful of fixes are in #[cfg(windows)] /
wasm32 arms that only the cross-target check pass exercises, and the GC-adjacent
changes (test.each rooting, html_rewriter protection, Strong::get) deserve close
reading — pointers to the riskiest items are in the campaign's synthesis notes.

Drain the Rust port's marker backlog: every audited TODO(port)/TODO(b2-*)
marker and PORT NOTE comment was individually resolved - fixed, deleted as
stale, untagged to a plain comment, or split into a tracked follow-up work
order. ~495 real code fixes, each citing the Zig sibling for intended
semantics; the 101 markers that remain are claimed by written work orders
for changes too risky to land in a bulk branch (GC rooting, arena lifetime
threading, cross-thread ownership restructures).

Verified: full debug build + smoke tests on linux-x64, cross-target cargo
check on windows/macos/linux x64/aarch64.
@robobun

robobun commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Purge remaining port-batch markers missed by the phase cleanup #30885 - Also removes port-batch/phase markers from the Zig-to-Rust migration across ~100 files, directly overlapping with this PR's systematic removal of PORT NOTE and port-process markers

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The head commit changed during the review from 0447e0d to 20da12b.

Walkthrough

This PR updates analytics feature plumbing, AST and bun_core runtime helpers, bundler task and output paths, CSS parser and serializer behavior, several FFI/platform mappings, and related tests and documentation comments.

Changes

Runtime and parser port updates

Layer / File(s) Summary
Analytics and AST contracts
src/analytics/*, src/ast/...
Analytics feature counters can alias bun_core globals, builtin modules can be recorded, schema EOF handling uses a helper function, and AST storage, identifier parsing, debug disablers, and loader/store traits are updated.
Core runtime and collections
src/bun_core/..., src/collections/..., src/crash_handler/..., src/errno/lib.rs, src/clap/..., src/dotenv/..., src/brotli/lib.rs
Core runtime code adds Win32 errno-name plumbing, spawn-with-null-stdin support, TODO panic counting, explicit zeroed output initialization, new IO adapters, collection API/test updates, and crash-reporting gating/counting changes.
Bundler flow and output wiring
src/bundler/...
Bundler dependencies now include analytics, parse/link/output paths are reshaped around borrowing and task flow, Step 5 gets parallel scaffolding, output entry-point indexing changes for server components, and chunk/post-process helpers add small API and lifecycle updates.
CSS pipeline and edge adapters
src/css/..., src/css_jsc/..., src/cares_sys/c_ares.rs, src/event_loop/..., src/exe_format/...
CSS parsing, hashing, bitset sizing, newline tracking, border-image fallback ownership, selector debug/display formatting, identifier map hashing, and color conversion paths are updated, with smaller FFI and platform-support contract changes alongside them.

Suggested reviewers

  • RiskyMH
  • dylan-conway

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
src/bundler/ThreadPool.rs (1)

150-166: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Increment REF_COUNT in the post-lock initialized path.

If another thread initializes the IO pool before this thread acquires MUTEX, this branch returns a live pool without counting the new owner. That lets shutdown() observe REF_COUNT == 1 while a ThreadPool still holds io_pool, so the static pool can be dropped while it is still in use.

💡 Proposed fix
         if REF_COUNT.load(Ordering::Relaxed) == 0 {
             // SAFETY: we hold MUTEX and REF_COUNT == 0, so no other thread is reading THREAD_POOL.
             unsafe {
                 (*THREAD_POOL.get()).write(ThreadPoolLib::ThreadPool::init(
                     ThreadPoolLib::Config {
@@
             }
             // 2 means initialized and referenced by one `ThreadPool`.
             REF_COUNT.store(2, Ordering::Release);
         } else {
-            // Zig fell through to `return &thread_pool` without
-            // bumping the ref count here, which is a latent bug in the source
-            // (the racing acquirer's reference isn't counted). Mirrored.
+            // Another thread initialized the pool while we were waiting on the mutex.
+            REF_COUNT.fetch_add(1, Ordering::Relaxed);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bundler/ThreadPool.rs` around lines 150 - 166, The else branch that
returns the already-initialized static pool must increment the global reference
counter so new owners are counted; update the branch that runs when
REF_COUNT.load(...) != 0 to atomically increment REF_COUNT (e.g., fetch_add(1,
...) with appropriate ordering) while still holding MUTEX (or immediately after
verifying initialization) before returning the THREAD_POOL pointer created via
ThreadPoolLib::ThreadPool::init, so shutdown() will see the correct live owner
count for REF_COUNT and the static THREAD_POOL won't be dropped while still in
use.
src/css/properties/grid.rs (2)

525-557: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle . null-cell tokens instead of rejecting them.

The . branch still falls through into the name-codepoint path, so any row containing an unnamed area fails to parse. That makes spec-valid values like "header . sidebar" invalid.

Suggested fix
             column += 1;
 
             if strings::starts_with_char(rest, b'.') {
-                // TODO(port): the Zig original falls through here without `continue` —
-                // likely a bug (per upstream lightningcss, a `.` null-cell token should
-                // push None and continue; as written, any string containing `.` fails
-                // the name-codepoint check below and the whole value fails to parse).
-                // Mirroring the observable Zig control flow for now (Zig dead-advances
-                // `string = rest[idx..]`; this block is empty — same outcome).
+                let dot_len = rest.iter().take_while(|&&c| c == b'.').count();
+                tokens.append(None);
+                string = &rest[dot_len..];
+                continue;
             }
 
             let starts_with_name_codepoint = 'brk: {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/properties/grid.rs` around lines 525 - 557, When
strings::starts_with_char(rest, b'.') is true, treat it as a null-cell: append a
None entry to tokens, advance string past the '.' (e.g., string = &rest[1..])
and continue the parsing loop instead of falling through into the name-codepoint
logic; this fixes handling of unnamed areas (update the block around
strings::starts_with_char(rest, b'.') to tokens.append(None), advance string,
and continue while preserving bump usage where needed).

470-500: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse all grid-template-areas rows, not just the first one.

This only attempts expect_string() once, so multi-row declarations leave subsequent rows unparsed, and non-none inputs with no leading string can fall through to Areas { columns: 0, areas: [] } instead of erroring. This breaks valid grid-template-areas values with more than one row.

Suggested fix
-        if let Ok(s) = input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>)) {
+        while let Ok(s) = input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>)) {
             // SAFETY: `s` points to a slice returned by `expect_string`, which is backed by the
             // parser's input arena and remains valid for the duration of this parse.
             let s = unsafe { crate::arena_str(s) };
             let parsed_columns = match Self::parse_string(input.arena(), s, &mut tokens) {
                 Ok(v) => v,
@@
             row += 1;
             // The final `row += 1` is dead on the last iteration but read on
             // the next; `unused_assignments` can't see that. Touch it.
             let _ = row;
         }
+
+        if row == 0 {
+            return Err(input.new_custom_error(css::ParserError::invalid_declaration));
+        }
 
         Ok(GridTemplateAreas::Areas {
             columns,
             areas: tokens,
         })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/properties/grid.rs` around lines 470 - 500, The code only calls
input.try_parse(|i| i.expect_string()) once, so subsequent grid-template-areas
rows are ignored; change the single if let Ok(s) = ... block into a loop (while
let Ok(s) = input.try_parse(|i|
i.expect_string().map(std::ptr::from_ref::<[u8]>))) and repeat the
parsing/column-consistency logic for each row using the existing parse_string,
row, columns, and tokens variables, keeping the unsafe crate::arena_str
conversion inside the loop; after the loop, if row == 0 handle the non-string
case by returning an appropriate parse error (unless the token is the keyword
`none`), so inputs with no leading string no longer silently fall through to
Areas { columns: 0, areas: [] } but instead error correctly.
src/css/values/color.rs (1)

2397-2403: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Accept the standards-compliant a98-rgb colorspace name here.

This dispatch only matches a99-rgb, but the earlier from handling in the same function family already recognizes a98-rgb. As written, color(a98-rgb …) falls into the unexpected-token path even though the file otherwise models A98.

Suggested fix
-        b"a99-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
+        b"a98-rgb" | b"a99-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/values/color.rs` around lines 2397 - 2403, The match arm in the
colorspace dispatch uses the incorrect literal b"a99-rgb", causing valid
"a98-rgb" inputs to be rejected; update the match in the
crate::match_ignore_ascii_case! block (the arm that constructs
PredefinedColor::A98 via A98 { r: a, g: b, b: c, alpha }) to match b"a98-rgb"
instead so it aligns with the earlier from handling and the A98
type/PredefinedColor::A98 variant.
src/css/media_query.rs (1)

1054-1060: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize equality media ranges with =.

MediaFeatureComparison::Equal currently emits -, so a parsed equality range round-trips as invalid CSS instead of (feature = value). If the Zig side still has this behavior, this is the spot to intentionally diverge rather than preserve the bug.

Suggested fix
         match self {
-            MediaFeatureComparison::Equal => dest.delim(b'-', true),
+            MediaFeatureComparison::Equal => dest.delim(b'=', true),
             MediaFeatureComparison::GreaterThan => dest.delim(b'>', true),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/media_query.rs` around lines 1054 - 1060, The serializer is emitting
'-' for equality which breaks round-trip; in MediaFeatureComparison::Equal
inside to_css replace the call that writes '-' with one that writes '=' (i.e.
change the delimiter passed to dest.delim from b'-' to b'=') so equality media
ranges serialize as "(feature = value)"; keep the same call site (to_css ->
dest.delim) and ensure surrounding behavior (the true flag) remains unchanged.
src/css/properties/font.rs (1)

748-755: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The font shorthand still can't parse variant-caps in the pre-size slot.

variant_caps is initialized to None, so gating FontVariantCaps::parse_css2 behind variant_caps.is_some() makes this branch unreachable until after a value is already set. That drops valid shorthands like italic small-caps 16px serif. This needs to stay is_none() even if Zig had the inverted condition.

🐛 Suggested fix
-            if variant_caps.is_some() {
+            if variant_caps.is_none() {
                 // Zig has `if (variant_caps != null)` here — preserved verbatim (likely upstream bug; should be `== null`)
                 if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {
                     variant_caps = Some(value);
                     count += 1;
                     continue;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/properties/font.rs` around lines 748 - 755, The branch that attempts
to parse FontVariantCaps::parse_css2 is incorrectly guarded by
variant_caps.is_some(), making it unreachable because variant_caps starts as
None; change the condition to variant_caps.is_none() so the if let Ok(value) =
input.try_parse(FontVariantCaps::parse_css2) branch can run when no value is yet
set, assign variant_caps = Some(value), increment count, and continue (the code
around the variant_caps variable and the input.try_parse call should be the
target).
src/collections/linear_fifo.rs (1)

119-145: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Documented UB is still reachable here.

StaticBuffer still reinterprets the entire [MaybeUninit<T>; N] as &[T]/&mut [T], so any path that exposes unwritten slots creates references to uninitialized T. The new comment correctly calls out that in-tree FIFO element types already violate the “any bit pattern is valid” assumption, which makes this a real soundness bug, not just deferred cleanup. Either restrict these buffers to byte/MaybeUninit-safe element types or change the accessor surface to operate on MaybeUninit<T> for unwritten regions before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/linear_fifo.rs` around lines 119 - 145, StaticBuffer
currently reinterprets the whole [MaybeUninit<T>; N] as &[T] / &mut [T] (see
as_slice, as_mut_slice using assume_init_slice/_mut) which creates references to
uninitialized T; change the accessor surface to expose &[MaybeUninit<T>] and
&mut [MaybeUninit<T>] instead and update the LinearFifoBuffer trait signatures
accordingly so callers must assume-init only the logically-written subranges
(use assume_init_slice/_mut only on those subranges), or alternatively add a
strict trait bound restricting T to a POD/any-bit-pattern-safe marker if you
prefer to keep returning &[T]; pick one approach and apply it consistently to
StaticBuffer::as_slice, StaticBuffer::as_mut_slice and the LinearFifoBuffer
trait so no path casts the full buffer to &[T] over unwritten slots.
src/bundler/options.rs (1)

382-400: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the missing stdin name for Loader::Jsonc.

Line 383 makes omissions silent, and this initializer never assigns Loader::Jsonc. That means JSONC stdin now gets an empty pseudo-filename instead of an extension-bearing one, which can degrade diagnostics and any downstream extension-based behavior.

Suggested fix
         map[Loader::File] = b"input";
         map[Loader::Json] = b"input.json";
+        map[Loader::Jsonc] = b"input.jsonc";
         map[Loader::Toml] = b"input.toml";
         map[Loader::Yaml] = b"input.yaml";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bundler/options.rs` around lines 382 - 400, stdin_name_map currently
never assigns a pseudo-filename for Loader::Jsonc, leaving its entry as an empty
slice; update the stdin_name_map initializer (function stdin_name_map) to set
map[Loader::Jsonc] = b"input.jsonc" so JSONC stdin receives an extension-bearing
name for diagnostics and extension-based logic.
src/ast/lib.rs (1)

3443-3452: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make DebugOnlyDisablerScope thread-affine (!Send)

DebugOnlyDisablerScope<T> is currently just PhantomData<T>, so it remains auto-Send when T: Send. On Drop it calls DebugOnlyDisabler::<T>::enable(), which mutates debug_disabler_state::DISABLED stored in std::thread_local!. Dropping the guard on another thread therefore operates on the wrong TLS slot, hitting the expect("DebugOnlyDisabler::enable without matching disable") panic and/or leaving the originating thread’s disable count unbalanced.

Suggested fix
-        DebugOnlyDisablerScope(core::marker::PhantomData)
+        DebugOnlyDisablerScope(core::marker::PhantomData, core::marker::PhantomData)
...
-pub struct DebugOnlyDisablerScope<T: 'static>(core::marker::PhantomData<T>);
+pub struct DebugOnlyDisablerScope<T: 'static>(
+    core::marker::PhantomData<T>,
+    core::marker::PhantomData<std::rc::Rc<()>>,
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ast/lib.rs` around lines 3443 - 3452, The guard is currently auto-Send
because it only holds PhantomData<T>, so dropping it on a different thread can
access the wrong thread-local state; change
DebugOnlyDisablerScope<T>(core::marker::PhantomData<T>) to carry a PhantomData
of a non-Send marker (for example core::marker::PhantomData<std::rc::Rc<()>>) so
the type becomes !Send, update the constructor DebugOnlyDisabler::scope to
create the new PhantomData variant, and keep the Drop impl that calls
DebugOnlyDisabler::enable() unchanged.
src/css/selectors/parser.rs (1)

3725-3747: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Lowercase the full attribute local name for HTML case-sensitivity detection (not only the suffix after the first uppercase).

When local_name contains an uppercase byte, local_name_lower is built from &local_name[first_uppercase..] (only the suffix), then passed into attribute_flags.to_case_sensitivity(local_name_lower.v(), ...). For AttributeFlags::CaseSensitivityDependsOnName, to_case_sensitivity() checks HTML case-insensitive attribute names like dir/rel via is_html_case_insensitive_attribute(local_name), so selectors such as [dIr] (first uppercase not at offset 0) can be misclassified as case-sensitive.

🐛 Proposed fix
-        if let Some(first_uppercase) = first_uppercase {
-            let str_ = &local_name[first_uppercase..];
-            // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`).
-            let lowered: *const [u8] = arena_lowercase(input.arena(), str_);
+        if first_uppercase.is_some() {
+            // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`).
+            let lowered: *const [u8] = arena_lowercase(input.arena(), local_name);
             break 'brk (Ident { v: lowered }, false);
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/selectors/parser.rs` around lines 3725 - 3747, The current code only
lowercases the suffix after the first uppercase, causing misclassification for
names like "dIr"; change the logic around
local_name_lower/local_name_is_ascii_lowercase so that when any uppercase is
found you call arena_lowercase(input.arena(), local_name) to produce a
fully-lowercased Ident (use Ident { v: lowered }) and set
local_name_is_ascii_lowercase = false; keep the existing branch for the
no-uppercase case that returns Ident { v: std::ptr::from_ref::<[u8]>(local_name)
} and true; ensure this updated local_name_lower is what gets passed into
attribute_flags.to_case_sensitivity(...) so is_html_case_insensitive_attribute
sees the full lowercased name.
src/css/css_parser.rs (3)

5663-5676: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

split_source_map() keeps the delimiter and drops EOF-terminated URLs.

When the first delimiter is found, &contents[..i + 1] includes that delimiter byte in the stored URL. And if the directive payload runs to EOF, the function returns None instead of the whole URL. So /*# sourceMappingURL=app.css.map*/ is ignored entirely, while /*# sourceMappingURL=app.css.map */ stores a trailing space/newline.

Suggested fix
     for (i, &c) in contents.iter().enumerate() {
         match c {
             b' ' | b'\t' | FORM_FEED_BYTE | b'\r' | b'\n' => {
-                return Some(&contents[0..i + 1]);
+                return Some(&contents[0..i]);
             }
             _ => {}
         }
     }
-    None
+    Some(contents)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/css_parser.rs` around lines 5663 - 5676, split_source_map currently
returns &contents[..i+1] (including the delimiter) and returns None when no
delimiter is found, which drops EOF-terminated URLs; change the behavior so when
a delimiter byte is matched in split_source_map (matching bytes in contents via
the for (i, &c) loop) you return the slice excluding the delimiter (e.g.,
&contents[0..i]) and if the loop completes with no delimiter found return
Some(contents) so EOF-terminated URLs are preserved.

2015-2047: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the layer context on the error path.

parse_style_block(input)? can return after on_layer_rule(), push_to_enclosing_layer(), or bump_anon_layer_count(1) have already mutated parser state. In recovery mode, later rules then inherit the wrong enclosing layer / anonymous-layer depth.

Suggested fix
                     T::on_layer_rule(this.at_rule_parser, &layer);
                     let old_len = T::enclosing_layer_length(this.at_rule_parser);
-                    if name.is_some() {
+                    let is_anon = name.is_none();
+                    if name.is_some() {
                         // Drain the sole element by value — avoids a second clone.
                         T::push_to_enclosing_layer(this.at_rule_parser, layer.swap_remove(0));
                     } else {
                         T::bump_anon_layer_count(this.at_rule_parser, 1);
                     }
+                    scopeguard::defer! {
+                        if is_anon {
+                            T::bump_anon_layer_count(this.at_rule_parser, -1);
+                        }
+                        T::reset_enclosing_layer(this.at_rule_parser, old_len);
+                    }

                     let rules = this.parse_style_block(input)?;

-                    if name.is_none() {
-                        T::bump_anon_layer_count(this.at_rule_parser, -1);
-                    }
-                    T::reset_enclosing_layer(this.at_rule_parser, old_len);
-
                     this.rules

As per coding guidelines, "Prefer RAII (Drop) over manual cleanup in Rust code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/css_parser.rs` around lines 2015 - 2047, The AtRule Prelude Layer
branch mutates parser state (calls T::on_layer_rule, possibly
T::push_to_enclosing_layer or T::bump_anon_layer_count) before calling
parse_style_block(input) and does not restore state if parse_style_block returns
an error; create an RAII guard (e.g., LayerContextGuard) inside the
AtRulePrelude::Layer handling that captures old_len, whether a name was pushed
or anon count bumped, and on Drop calls
T::reset_enclosing_layer(this.at_rule_parser, old_len) and undoes the anon bump
or removed push as appropriate, and ensure the guard is dismissed/disabled only
after parse_style_block succeeds so the cleanup runs on error paths (refer to
symbols: parse_style_block, T::on_layer_rule, T::push_to_enclosing_layer,
T::bump_anon_layer_count, T::reset_enclosing_layer, T::enclosing_layer_length,
the local name variable).

3015-3084: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This only plucks the first leading @import.

Both passes break on the same iteration that flips saw_imports to true, so a stylesheet with multiple top-level imports only counts and rewrites the first one. The remaining imports stay in self.rules with stale import-record indexes.

Suggested fix
             {
                 let mut saw_imports = false;
                 for rule in self.rules.v.iter() {
                     match rule {
                         CssRule::LayerBlock(_) => {}
                         CssRule::Import(_) => {
-                            if !saw_imports {
-                                saw_imports = true;
-                            }
+                            saw_imports = true;
                             count += 1;
                         }
                         CssRule::Unknown(u) => {
                             if u.name == b"tailwind" {
                                 continue;
                             }
                         }
-                        _ => {}
-                    }
-                    if saw_imports {
-                        break;
+                        _ if saw_imports => break,
+                        _ => {}
                     }
                 }
             }
@@
             let mut saw_imports = false;
             for rule in self.rules.v.iter_mut() {
                 match rule {
                     // TODO: layer, might have imports
                     CssRule::LayerBlock(_) => {}
                     CssRule::Import(import_rule) => {
-                        if !saw_imports {
-                            saw_imports = true;
-                        }
+                        saw_imports = true;
                         let import_record_idx = u32::try_from(new_import_records.len()).unwrap();
                         import_rule.import_record_idx = import_record_idx;
                         new_import_records.push(ImportRecord {
@@
                         let old = core::mem::replace(rule, CssRule::Ignored);
                         // PERF(port): was appendAssumeCapacity
                         out.v.push(old);
                     }
                     CssRule::Unknown(u) => {
                         if u.name == b"tailwind" {
                             continue;
                         }
                     }
-                    _ => {}
-                }
-                if saw_imports {
-                    break;
+                    _ if saw_imports => break,
+                    _ => {}
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/css_parser.rs` around lines 3015 - 3084, The two loops that scan
self.rules.v set saw_imports on the first CssRule::Import and then
unconditionally break at the end of that iteration, so only the first leading
`@import` is counted/rewritten; update both loops (the initial counting loop and
the rewrite loop that assigns import_record_idx and pushes to out.v) to not
break immediately when saw_imports becomes true but instead continue processing
subsequent rules and only break when you encounter the first non-import rule
after you have already seen imports (i.e., break only when saw_imports == true
and the current rule is neither CssRule::Import nor an allowed-leading rule like
CssRule::LayerBlock or CssRule::Unknown with name == b"tailwind"); ensure
new_import_records.len() and import_rule.import_record_idx logic runs for each
Import encountered so import_record_idx values remain correct for all leading
imports.
src/bundler/transpiler.rs (1)

3042-3054: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

MacroRemap insertion isn’t a fallible Result drop: m.insert(k, inner) is StringArrayHashMap::insert(&mut self, ..., ...) -> Option<V> (not Result), and its backing push_entry path doesn’t return AllocError, so there’s no Result here to silently drop (see src/collections/array_hash_map.rs).
Optional: if you want AllocError-to-bun error conversion symmetry with put (StringArrayHashMap::put(...) -> Result<(), AllocError>), switch this to m.put(k, inner)? / bun_core::handle_oom(m.put(k, inner)) instead of insert.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bundler/transpiler.rs` around lines 3042 - 3054, The current code uses
MacroRemap::default() and calls m.insert(k, inner) where
StringArrayHashMap::insert returns Option<V> and is not fallible; replace the
insert call with the fallible put API to preserve AllocError->bun error
handling: call m.put(k, inner)? (or wrap with bun_core::handle_oom(m.put(k,
inner))) when iterating self.options.macro_remap so failures propagate; keep the
surrounding inner = v.clone().map_err(|_| bun_core::err!("OutOfMemory"))? logic
and otherwise return m as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/analytics/lib.rs`:
- Around line 123-126: The macro arm matching "(`@storage` $(#[$doc:meta])*
$ident:ident, $core:ident)" currently applies #[allow(non_upper_case_globals)]
to the "pub use ::bun_core::Global::features::$core as $ident;" line which is
ineffective and triggers Clippy; remove the #[allow(non_upper_case_globals)]
attribute from the pub use arm and ensure that the attribute remains only on the
actual "pub static $ident: AtomicUsize" arm so the allow applies to the real
static/const declaration.

In `@src/ast/lib.rs`:
- Around line 1473-1489: The identifier scanner currently only handles a leading
backslash escape after the first iter.next(), so an identifier starting with a
"\u{...}" is truncated; update the initial branch where you check "if
bun_core::identifier::is_identifier_start(cursor.c) || cursor.c == '\\' as i32"
to, when cursor.c == '\\' (the initial cursor), perform the same
backslash-unicode-skip logic used inside the while (the checks on text[cursor.i
+ 1] == b'u' and text[cursor.i + 2] == b'{' and advancing cursor.i until the
closing '}'), so the starting cursor is advanced past a leading "\u{...}" before
entering the main iter.next() loop in the identifier scan (use the same
cursor/text bounds checks as the inner block to avoid out-of-bounds).

In `@src/ast/new_store.rs`:
- Around line 21-27: The sealing module is currently public which allows
downstream crates to implement sealed::Sealed and break the invariants; change
the declaration from `pub mod sealed` to a non-public module (e.g. `mod sealed`
or `pub(crate) mod sealed`) so only this crate can implement `sealed::Sealed`,
leaving the `pub trait StoredIn<S>: sealed::Sealed<S> {}` line unchanged and
ensure any `new_store!` macro references remain using
`$crate::new_store::sealed::Sealed` so the macro still points to the crate-local
sealed trait.

In `@src/brotli/lib.rs`:
- Around line 442-455: The flush() implementation on BrotliWriter currently
calls self.compressor.write(b"", false) which uses the normal flush_op; change
it to invoke the compressor's full flush operation so the encoder emits any
pending bytes before returning — e.g. call the compressor with full_flush_op
(replace self.compressor.write(b"", false) with a call that uses full_flush_op,
e.g. self.compressor.write(b"", self.compressor.full_flush_op() or
full_flush_op()) and then keep writing the returned out to self.input_writer and
flushing as before in BrotliWriter::flush.

In `@src/bun_core/output.rs`:
- Around line 1517-1518: The issue is that removing per-scope buffers means
self.lock only serializes access to a single ScopedLogger while all scopes call
into the shared SCOPED_FILE_WRITER, allowing concurrent write_fmt/write_str
sequences to interleave; fix by introducing a shared sink lock around the shared
writer so write_fmt and flush are performed atomically (e.g., add a global Mutex
or RwLock used by SCOPED_FILE_WRITER and acquire it in
ScopedLogger::write_fmt/flush before calling into the writer), or alternatively
restore a per-scope buffer in ScopedLogger and emit a single write to
SCOPED_FILE_WRITER, ensuring no partial write_str calls are issued to the shared
sink.

In `@src/bun_core/util.rs`:
- Around line 4914-4917: spawn_sync_inherit_impl currently indexes argv[0]
without checking for an empty slice which can panic; guard against empty argv at
the start of spawn_sync_inherit_impl and return a proper Err(crate::Error) (the
same failure path/semantics used by the Windows implementation) instead of
indexing into argv[0] or aborting, updating any downstream uses of argv[0]
within the function to assume non-empty after that check; reference
spawn_sync_inherit_impl, SpawnStatus and crate::Error when making this change.

In `@src/bundler/linker.rs`:
- Around line 377-395: The code currently always calls
dupe(modkey.hash_name(...)) which interns a fresh process-lifetime string on
every lookup (via dupe) while IS_CACHE_ENABLED is hard-disabled, causing
unbounded memory growth; instead, use the hashed_filenames cache before
interning and only call dupe when inserting a new entry: compute the key with
bun_wyhash::hash(file_path.text), check self.hashed_filenames for an existing
entry and return it if present, otherwise call modkey.hash_name into
hash_name_buf, dupe the result and insert it into self.hashed_filenames; also
guard against hash collisions by verifying the string equality before returning
an entry.

In `@src/clap/args.rs`:
- Around line 339-344: Rename the parameter `allocations` in the test helper
`test_shell_iterator_ok` to reflect that it measures owned-result count (e.g.,
`expected_owned_results` or `expected_owned_count`), update all uses inside the
function (`owned_results` comparisons/assertions) and any call sites, and adjust
the leading comment to say "owned-result count" / "borrowed (zero-copy) fast
path" instead of "allocations"; apply the same renaming and comment tweak to the
other matching test helper instance referenced in the diff.

In `@src/collections/linear_fifo.rs`:
- Around line 805-817: The Write impl for LinearFifo<u8, B> should not map
capacity-exhaustion on fixed buffers to OutOfMemory; change the body of
std::io::Write::write (the impl for LinearFifo::write) to first compute the
available writable_length() and, for non-dynamic buffers (when B::DYNAMIC is
false), write at most that many bytes and return Ok(n) (return Ok(0) if full)
instead of mapping an AllocError to ErrorKind::OutOfMemory; only treat
AllocError as true OOM when B::DYNAMIC is true (i.e., an actual allocation was
attempted by ensure_total_capacity), preserving the existing mapping to
std::io::ErrorKind::OutOfMemory in that dynamic case.

In `@src/crash_handler/lib.rs`:
- Around line 946-948: The branch currently gates printing the unsupported-libuv
banner on the global bun_analytics::features::unsupported_uv_function counter
but the payload is stored in the thread-local UNSUPPORTED_UV_FUNCTION, causing
cross-thread leakage; change the gating so the banner is printed only when the
current thread's UNSUPPORTED_UV_FUNCTION TLS slot is set (e.g., check
UNSUPPORTED_UV_FUNCTION.with(|v| v.is_some() or peek the TLS value) before
emitting the banner) and keep bun_analytics::features::unsupported_uv_function
as a process-global analytics counter only; apply the same TLS-based gatefix to
the other similar branches that reference
bun_analytics::features::unsupported_uv_function and UNSUPPORTED_UV_FUNCTION
(including the branches associated with CrashHandler__unsupportedUVFunction()).

In `@src/css/css_parser.rs`:
- Around line 3091-3106: contains_tailwind_directives currently checks for
CssRule::Custom(_) which is never produced because
BundlerAtRuleParser::parse_prelude maps unknown bundler directives to
AtRulePrelude::Unknown; update the function
(StyleSheet<BundlerAtRule>::contains_tailwind_directives) to detect Tailwind
at-rules by matching CssRule::AtRule(at_rule) and checking at_rule.prelude ==
AtRulePrelude::Unknown (and at_rule.name == "tailwind" or matches the expected
tailwind directive name(s)), returning true when found; keep the existing
handling for CssRule::LayerBlock and CssRule::Import but replace the unreachable
Custom arm with the new AtRule check.

In `@src/css/properties/border.rs`:
- Around line 1628-1635: The macro arm for ($id:ident) currently builds an
UnparsedProperty into the local upppppppppp and records the flushed property but
never enqueues it to dest, dropping rewritten border-block-* declarations; fix
by appending the rewritten unparsed property to dest after calling
context.add_unparsed_fallbacks (i.e., push or extend dest with upppppppppp
returned from unparsed.with_property_id) so that the rewritten physical
border-top/border-bottom properties are emitted; keep the existing call to
self.flushed_properties.insert(BorderProperty::try_from_property_id(PropertyIdTag::$id).unwrap()).

In `@src/css/selectors/parser.rs`:
- Around line 1651-1653: The debug/display mismatch: make the debug() helpers
only available for the concrete impl_::Selectors and fix SelectorDebugFmt
output; specifically, move or reimplement GenericSelectorList<Impl>::debug() and
GenericSelector<Impl>::debug() as impl blocks for
GenericSelectorList<impl_::Selectors> and GenericSelector<impl_::Selectors> (so
they return SelectorListDebugFmt/SelectorDebugFmt that match the existing
fmt::Display impl for SelectorListDebugFmt<'a, impl_::Selectors> and
SelectorDebugFmt<'a, impl_::Selectors>), and in SelectorDebugFmt::fmt ensure
both branches write the closing ')' (append ')' in the Ok branch and in the Err
branch) and replace the Err branch's writeln! with write! so no stray newline is
emitted; leave parse_attribute_selector logic as-is.
- Around line 1964-1968: The SelectorDebugFmt implementation writes the opening
"Selector(" but never emits the closing ")" and the Err branch wrongly uses
writeln! (inserting a newline); update the match arms in the Debug impl for
SelectorDebugFmt so both arms call write! (not writeln!) and append the closing
parenthesis—i.e., for Ok(()) write the buffer then write!(")") (or include the
")" in the same write! that writes the buffer) and for Err(e) write the error
message without a newline and then write!(")") so the formatted output is a
single line like Selector(<content>) in both success and error cases.

In `@src/css/values/color.rs`:
- Around line 2497-2503: The match arm for ColorSpaceName::XyzD50 dispatches to
interpolate::<XYZd65>, causing xyz-d50 mixes to run in the wrong colorspace;
update that arm to call first_color.interpolate::<XYZd50>(p1, &second_color, p2,
hue_method) (replace XYZd65 with XYZd50) so ColorSpaceName::XyzD50 uses the
correct XYZd50 implementation.
- Around line 1507-1520: Construct and return the specific enum variants instead
of always using LABColor::Lab: update new_oklab to return LABColor::OkLab(OKLAB
{ l, a, b, alpha }), update new_lch to return LABColor::Lch(LCH { l, a, b, alpha
}), and update new_oklch to return LABColor::OkLch(OKLCH { l, a, b, alpha }) so
each constructor preserves its intended colorspace variant (modify the
variant/struct names to match the exact identifiers used in the file if they
differ).

In `@src/errno/lib.rs`:
- Around line 380-399: Add a Windows-gated unit test for win32_errno_name that
asserts known mappings (e.g., GetLastError codes like ERROR_FILE_NOT_FOUND /
ERROR_ACCESS_DENIED map to their SystemErrno names) and that an unmapped or
out-of-range u32 returns None; place it under #[cfg(windows)] in the same test
module so it exercises win32_errno_name (and the u16::try_from fallback)
directly. Use the win32_errno_name function name to locate the code and include
at least two mapped codes and one unmapped/high u32 value to lock down both
translation and fallback behavior.

---

Outside diff comments:
In `@src/ast/lib.rs`:
- Around line 3443-3452: The guard is currently auto-Send because it only holds
PhantomData<T>, so dropping it on a different thread can access the wrong
thread-local state; change
DebugOnlyDisablerScope<T>(core::marker::PhantomData<T>) to carry a PhantomData
of a non-Send marker (for example core::marker::PhantomData<std::rc::Rc<()>>) so
the type becomes !Send, update the constructor DebugOnlyDisabler::scope to
create the new PhantomData variant, and keep the Drop impl that calls
DebugOnlyDisabler::enable() unchanged.

In `@src/bundler/options.rs`:
- Around line 382-400: stdin_name_map currently never assigns a pseudo-filename
for Loader::Jsonc, leaving its entry as an empty slice; update the
stdin_name_map initializer (function stdin_name_map) to set map[Loader::Jsonc] =
b"input.jsonc" so JSONC stdin receives an extension-bearing name for diagnostics
and extension-based logic.

In `@src/bundler/ThreadPool.rs`:
- Around line 150-166: The else branch that returns the already-initialized
static pool must increment the global reference counter so new owners are
counted; update the branch that runs when REF_COUNT.load(...) != 0 to atomically
increment REF_COUNT (e.g., fetch_add(1, ...) with appropriate ordering) while
still holding MUTEX (or immediately after verifying initialization) before
returning the THREAD_POOL pointer created via ThreadPoolLib::ThreadPool::init,
so shutdown() will see the correct live owner count for REF_COUNT and the static
THREAD_POOL won't be dropped while still in use.

In `@src/bundler/transpiler.rs`:
- Around line 3042-3054: The current code uses MacroRemap::default() and calls
m.insert(k, inner) where StringArrayHashMap::insert returns Option<V> and is not
fallible; replace the insert call with the fallible put API to preserve
AllocError->bun error handling: call m.put(k, inner)? (or wrap with
bun_core::handle_oom(m.put(k, inner))) when iterating self.options.macro_remap
so failures propagate; keep the surrounding inner = v.clone().map_err(|_|
bun_core::err!("OutOfMemory"))? logic and otherwise return m as before.

In `@src/collections/linear_fifo.rs`:
- Around line 119-145: StaticBuffer currently reinterprets the whole
[MaybeUninit<T>; N] as &[T] / &mut [T] (see as_slice, as_mut_slice using
assume_init_slice/_mut) which creates references to uninitialized T; change the
accessor surface to expose &[MaybeUninit<T>] and &mut [MaybeUninit<T>] instead
and update the LinearFifoBuffer trait signatures accordingly so callers must
assume-init only the logically-written subranges (use assume_init_slice/_mut
only on those subranges), or alternatively add a strict trait bound restricting
T to a POD/any-bit-pattern-safe marker if you prefer to keep returning &[T];
pick one approach and apply it consistently to StaticBuffer::as_slice,
StaticBuffer::as_mut_slice and the LinearFifoBuffer trait so no path casts the
full buffer to &[T] over unwritten slots.

In `@src/css/css_parser.rs`:
- Around line 5663-5676: split_source_map currently returns &contents[..i+1]
(including the delimiter) and returns None when no delimiter is found, which
drops EOF-terminated URLs; change the behavior so when a delimiter byte is
matched in split_source_map (matching bytes in contents via the for (i, &c)
loop) you return the slice excluding the delimiter (e.g., &contents[0..i]) and
if the loop completes with no delimiter found return Some(contents) so
EOF-terminated URLs are preserved.
- Around line 2015-2047: The AtRule Prelude Layer branch mutates parser state
(calls T::on_layer_rule, possibly T::push_to_enclosing_layer or
T::bump_anon_layer_count) before calling parse_style_block(input) and does not
restore state if parse_style_block returns an error; create an RAII guard (e.g.,
LayerContextGuard) inside the AtRulePrelude::Layer handling that captures
old_len, whether a name was pushed or anon count bumped, and on Drop calls
T::reset_enclosing_layer(this.at_rule_parser, old_len) and undoes the anon bump
or removed push as appropriate, and ensure the guard is dismissed/disabled only
after parse_style_block succeeds so the cleanup runs on error paths (refer to
symbols: parse_style_block, T::on_layer_rule, T::push_to_enclosing_layer,
T::bump_anon_layer_count, T::reset_enclosing_layer, T::enclosing_layer_length,
the local name variable).
- Around line 3015-3084: The two loops that scan self.rules.v set saw_imports on
the first CssRule::Import and then unconditionally break at the end of that
iteration, so only the first leading `@import` is counted/rewritten; update both
loops (the initial counting loop and the rewrite loop that assigns
import_record_idx and pushes to out.v) to not break immediately when saw_imports
becomes true but instead continue processing subsequent rules and only break
when you encounter the first non-import rule after you have already seen imports
(i.e., break only when saw_imports == true and the current rule is neither
CssRule::Import nor an allowed-leading rule like CssRule::LayerBlock or
CssRule::Unknown with name == b"tailwind"); ensure new_import_records.len() and
import_rule.import_record_idx logic runs for each Import encountered so
import_record_idx values remain correct for all leading imports.

In `@src/css/media_query.rs`:
- Around line 1054-1060: The serializer is emitting '-' for equality which
breaks round-trip; in MediaFeatureComparison::Equal inside to_css replace the
call that writes '-' with one that writes '=' (i.e. change the delimiter passed
to dest.delim from b'-' to b'=') so equality media ranges serialize as "(feature
= value)"; keep the same call site (to_css -> dest.delim) and ensure surrounding
behavior (the true flag) remains unchanged.

In `@src/css/properties/font.rs`:
- Around line 748-755: The branch that attempts to parse
FontVariantCaps::parse_css2 is incorrectly guarded by variant_caps.is_some(),
making it unreachable because variant_caps starts as None; change the condition
to variant_caps.is_none() so the if let Ok(value) =
input.try_parse(FontVariantCaps::parse_css2) branch can run when no value is yet
set, assign variant_caps = Some(value), increment count, and continue (the code
around the variant_caps variable and the input.try_parse call should be the
target).

In `@src/css/properties/grid.rs`:
- Around line 525-557: When strings::starts_with_char(rest, b'.') is true, treat
it as a null-cell: append a None entry to tokens, advance string past the '.'
(e.g., string = &rest[1..]) and continue the parsing loop instead of falling
through into the name-codepoint logic; this fixes handling of unnamed areas
(update the block around strings::starts_with_char(rest, b'.') to
tokens.append(None), advance string, and continue while preserving bump usage
where needed).
- Around line 470-500: The code only calls input.try_parse(|i|
i.expect_string()) once, so subsequent grid-template-areas rows are ignored;
change the single if let Ok(s) = ... block into a loop (while let Ok(s) =
input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>))) and
repeat the parsing/column-consistency logic for each row using the existing
parse_string, row, columns, and tokens variables, keeping the unsafe
crate::arena_str conversion inside the loop; after the loop, if row == 0 handle
the non-string case by returning an appropriate parse error (unless the token is
the keyword `none`), so inputs with no leading string no longer silently fall
through to Areas { columns: 0, areas: [] } but instead error correctly.

In `@src/css/selectors/parser.rs`:
- Around line 3725-3747: The current code only lowercases the suffix after the
first uppercase, causing misclassification for names like "dIr"; change the
logic around local_name_lower/local_name_is_ascii_lowercase so that when any
uppercase is found you call arena_lowercase(input.arena(), local_name) to
produce a fully-lowercased Ident (use Ident { v: lowered }) and set
local_name_is_ascii_lowercase = false; keep the existing branch for the
no-uppercase case that returns Ident { v: std::ptr::from_ref::<[u8]>(local_name)
} and true; ensure this updated local_name_lower is what gets passed into
attribute_flags.to_case_sensitivity(...) so is_html_case_insensitive_attribute
sees the full lowercased name.

In `@src/css/values/color.rs`:
- Around line 2397-2403: The match arm in the colorspace dispatch uses the
incorrect literal b"a99-rgb", causing valid "a98-rgb" inputs to be rejected;
update the match in the crate::match_ignore_ascii_case! block (the arm that
constructs PredefinedColor::A98 via A98 { r: a, g: b, b: c, alpha }) to match
b"a98-rgb" instead so it aligns with the earlier from handling and the A98
type/PredefinedColor::A98 variant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 365c518e-c2f5-4bfb-9a5c-4fb23638e5cc

📥 Commits

Reviewing files that changed from the base of the PR and between bf70cc7 and b43c712.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (299)
  • src/analytics/lib.rs
  • src/analytics/schema.rs
  • src/api/lib.rs
  • src/ast/Cargo.toml
  • src/ast/ast_result.rs
  • src/ast/b.rs
  • src/ast/char_freq.rs
  • src/ast/e.rs
  • src/ast/expr.rs
  • src/ast/g.rs
  • src/ast/import_record.rs
  • src/ast/known_global.rs
  • src/ast/lib.rs
  • src/ast/loader.rs
  • src/ast/new_store.rs
  • src/ast/nodes.rs
  • src/ast/op.rs
  • src/ast/runtime.rs
  • src/ast/scope.rs
  • src/ast/server_component_boundary.rs
  • src/ast/stmt.rs
  • src/ast/symbol.rs
  • src/ast/target.rs
  • src/ast/ts.rs
  • src/base64/lib.rs
  • src/brotli/lib.rs
  • src/brotli_sys/brotli_c.rs
  • src/bun.js.rs
  • src/bun_alloc/MaxHeapAllocator.rs
  • src/bun_alloc/NullableAllocator.rs
  • src/bun_alloc/fallback/z.rs
  • src/bun_alloc/heap_breakdown.rs
  • src/bun_alloc/lib.rs
  • src/bun_alloc/maybe_owned.rs
  • src/bun_alloc/memory.rs
  • src/bun_bin/lib.rs
  • src/bun_core/Global.rs
  • src/bun_core/Progress.rs
  • src/bun_core/bounded_array.rs
  • src/bun_core/deprecated.rs
  • src/bun_core/env.rs
  • src/bun_core/env_var.rs
  • src/bun_core/external_shared.rs
  • src/bun_core/fmt.rs
  • src/bun_core/lib.rs
  • src/bun_core/output.rs
  • src/bun_core/result.rs
  • src/bun_core/string/MutableString.rs
  • src/bun_core/string/SmolStr.rs
  • src/bun_core/string/StringBuilder.rs
  • src/bun_core/string/StringJoiner.rs
  • src/bun_core/string/escapeRegExp.rs
  • src/bun_core/string/identifier.rs
  • src/bun_core/string/immutable.rs
  • src/bun_core/string/immutable/exact_size_matcher.rs
  • src/bun_core/string/immutable/unicode.rs
  • src/bun_core/string/mod.rs
  • src/bun_core/string/wtf.rs
  • src/bun_core/tty.rs
  • src/bun_core/util.rs
  • src/bun_core/wtf.rs
  • src/bundler/AstBuilder.rs
  • src/bundler/BundleThread.rs
  • src/bundler/Cargo.toml
  • src/bundler/Chunk.rs
  • src/bundler/DeferredBatchTask.rs
  • src/bundler/Graph.rs
  • src/bundler/HTMLScanner.rs
  • src/bundler/IndexStringMap.rs
  • src/bundler/LinkerContext.rs
  • src/bundler/LinkerGraph.rs
  • src/bundler/OutputFile.rs
  • src/bundler/ParseTask.rs
  • src/bundler/ServerComponentParseTask.rs
  • src/bundler/ThreadPool.rs
  • src/bundler/analyze_transpiled_module.rs
  • src/bundler/barrel_imports.rs
  • src/bundler/bundle_v2.rs
  • src/bundler/bundled_ast.rs
  • src/bundler/cache.rs
  • src/bundler/defines.rs
  • src/bundler/entry_points.rs
  • src/bundler/lib.rs
  • src/bundler/linker.rs
  • src/bundler/linker_context/MetafileBuilder.rs
  • src/bundler/linker_context/OutputFileListBuilder.rs
  • src/bundler/linker_context/StaticRouteVisitor.rs
  • src/bundler/linker_context/computeChunks.rs
  • src/bundler/linker_context/computeCrossChunkDependencies.rs
  • src/bundler/linker_context/convertStmtsForChunk.rs
  • src/bundler/linker_context/convertStmtsForChunkForDevServer.rs
  • src/bundler/linker_context/doStep5.rs
  • src/bundler/linker_context/findAllImportedPartsInJSOrder.rs
  • src/bundler/linker_context/findImportedFilesInCSSOrder.rs
  • src/bundler/linker_context/generateChunksInParallel.rs
  • src/bundler/linker_context/generateCodeForFileInChunkJS.rs
  • src/bundler/linker_context/generateCodeForLazyExport.rs
  • src/bundler/linker_context/generateCompileResultForCssChunk.rs
  • src/bundler/linker_context/generateCompileResultForHtmlChunk.rs
  • src/bundler/linker_context/generateCompileResultForJSChunk.rs
  • src/bundler/linker_context/postProcessCSSChunk.rs
  • src/bundler/linker_context/postProcessHTMLChunk.rs
  • src/bundler/linker_context/postProcessJSChunk.rs
  • src/bundler/linker_context/prepareCssAstsForChunk.rs
  • src/bundler/linker_context/renameSymbolsInChunk.rs
  • src/bundler/linker_context/scanImportsAndExports.rs
  • src/bundler/linker_context/writeOutputFilesToDisk.rs
  • src/bundler/options.rs
  • src/bundler/transpiler.rs
  • src/bundler_jsc/analyze_jsc.rs
  • src/bunfig/arguments.rs
  • src/bunfig/bunfig.rs
  • src/cares_sys/c_ares.rs
  • src/clap/args.rs
  • src/clap/comptime.rs
  • src/clap/lib.rs
  • src/clap/streaming.rs
  • src/collections/StaticHashMap.rs
  • src/collections/array_hash_map.rs
  • src/collections/array_list.rs
  • src/collections/bit_set.rs
  • src/collections/comptime_string_map.rs
  • src/collections/hive_array.rs
  • src/collections/lib.rs
  • src/collections/linear_fifo.rs
  • src/collections/pool.rs
  • src/collections/string_map.rs
  • src/crash_handler/CPUFeatures.rs
  • src/crash_handler/handle_oom.rs
  • src/crash_handler/lib.rs
  • src/csrf/lib.rs
  • src/css/context.rs
  • src/css/css_modules.rs
  • src/css/css_parser.rs
  • src/css/declaration.rs
  • src/css/dependencies.rs
  • src/css/error.rs
  • src/css/generics.rs
  • src/css/lib.rs
  • src/css/media_query.rs
  • src/css/printer.rs
  • src/css/properties/align.rs
  • src/css/properties/animation.rs
  • src/css/properties/background.rs
  • src/css/properties/border.rs
  • src/css/properties/border_image.rs
  • src/css/properties/border_radius.rs
  • src/css/properties/box_shadow.rs
  • src/css/properties/css_modules.rs
  • src/css/properties/custom.rs
  • src/css/properties/display.rs
  • src/css/properties/flex.rs
  • src/css/properties/font.rs
  • src/css/properties/grid.rs
  • src/css/properties/margin_padding.rs
  • src/css/properties/masking.rs
  • src/css/properties/mod.rs
  • src/css/properties/overflow.rs
  • src/css/properties/prefix_handler.rs
  • src/css/properties/properties_generated.rs
  • src/css/properties/properties_impl.rs
  • src/css/properties/shape.rs
  • src/css/properties/size.rs
  • src/css/properties/text.rs
  • src/css/properties/transform.rs
  • src/css/properties/transition.rs
  • src/css/properties/ui.rs
  • src/css/rules/container.rs
  • src/css/rules/counter_style.rs
  • src/css/rules/document.rs
  • src/css/rules/font_face.rs
  • src/css/rules/font_palette_values.rs
  • src/css/rules/import.rs
  • src/css/rules/keyframes.rs
  • src/css/rules/layer.rs
  • src/css/rules/media.rs
  • src/css/rules/mod.rs
  • src/css/rules/namespace.rs
  • src/css/rules/nesting.rs
  • src/css/rules/page.rs
  • src/css/rules/property.rs
  • src/css/rules/scope.rs
  • src/css/rules/starting_style.rs
  • src/css/rules/style.rs
  • src/css/rules/supports.rs
  • src/css/rules/unknown.rs
  • src/css/rules/viewport.rs
  • src/css/selectors/builder.rs
  • src/css/selectors/parser.rs
  • src/css/selectors/selector.rs
  • src/css/small_list.rs
  • src/css/sourcemap.rs
  • src/css/targets.rs
  • src/css/values/alpha.rs
  • src/css/values/angle.rs
  • src/css/values/calc.rs
  • src/css/values/color.rs
  • src/css/values/easing.rs
  • src/css/values/gradient.rs
  • src/css/values/ident.rs
  • src/css/values/image.rs
  • src/css/values/length.rs
  • src/css/values/percentage.rs
  • src/css/values/position.rs
  • src/css/values/ratio.rs
  • src/css/values/rect.rs
  • src/css/values/size.rs
  • src/css/values/syntax.rs
  • src/css/values/time.rs
  • src/css/values/url.rs
  • src/css_jsc/color_js.rs
  • src/css_jsc/css_internals.rs
  • src/dns/lib.rs
  • src/dotenv/env_loader.rs
  • src/errno/lib.rs
  • src/event_loop/AnyEventLoop.rs
  • src/event_loop/AnyTaskWithExtraContext.rs
  • src/event_loop/AutoFlusher.rs
  • src/event_loop/ConcurrentTask.rs
  • src/event_loop/DeferredTaskQueue.rs
  • src/event_loop/EventLoopTimer.rs
  • src/event_loop/ManagedTask.rs
  • src/event_loop/MiniEventLoop.rs
  • src/event_loop/SpawnSyncEventLoop.rs
  • src/event_loop/lib.rs
  • src/exe_format/elf.rs
  • src/exe_format/macho.rs
  • src/exe_format/pe.rs
  • src/glob/GlobWalker.rs
  • src/glob/lib.rs
  • src/glob/matcher.rs
  • src/highway/lib.rs
  • src/http/AsyncHTTP.rs
  • src/http/CertificateInfo.rs
  • src/http/Decompressor.rs
  • src/http/H2Client.rs
  • src/http/H3Client.rs
  • src/http/HTTPCertError.rs
  • src/http/HTTPContext.rs
  • src/http/HTTPRequestBody.rs
  • src/http/HTTPThread.rs
  • src/http/HeaderBuilder.rs
  • src/http/HeaderValueIterator.rs
  • src/http/Headers.rs
  • src/http/InternalState.rs
  • src/http/ProxyTunnel.rs
  • src/http/SendFile.rs
  • src/http/Signals.rs
  • src/http/ThreadSafeStreamBuffer.rs
  • src/http/h2_client/ClientSession.rs
  • src/http/h2_client/PendingConnect.rs
  • src/http/h2_client/Stream.rs
  • src/http/h2_client/dispatch.rs
  • src/http/h2_client/encode.rs
  • src/http/h3_client/AltSvc.rs
  • src/http/h3_client/ClientSession.rs
  • src/http/h3_client/encode.rs
  • src/http/lib.rs
  • src/http/lshpack.rs
  • src/http/ssl_config.rs
  • src/http/websocket.rs
  • src/http/zlib.rs
  • src/http_jsc/headers_jsc.rs
  • src/http_jsc/method_jsc.rs
  • src/http_jsc/websocket_client.rs
  • src/http_jsc/websocket_client/CppWebSocket.rs
  • src/http_jsc/websocket_client/WebSocketDeflate.rs
  • src/http_jsc/websocket_client/WebSocketProxy.rs
  • src/http_jsc/websocket_client/WebSocketProxyTunnel.rs
  • src/http_jsc/websocket_client/WebSocketUpgradeClient.rs
  • src/http_types/ETag.rs
  • src/http_types/Method.rs
  • src/http_types/MimeType.rs
  • src/http_types/URLPath.rs
  • src/http_types/h2.rs
  • src/ini/lib.rs
  • src/install/ConfigVersion.rs
  • src/install/NetworkTask.rs
  • src/install/PackageInstall.rs
  • src/install/PackageInstaller.rs
  • src/install/PackageManager.rs
  • src/install/PackageManager/CommandLineArguments.rs
  • src/install/PackageManager/PackageJSONEditor.rs
  • src/install/PackageManager/PackageManagerDirectories.rs
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/install/PackageManager/PackageManagerLifecycle.rs
  • src/install/PackageManager/PackageManagerOptions.rs
  • src/install/PackageManager/PackageManagerResolution.rs
  • src/install/PackageManager/PopulateManifestCache.rs
  • src/install/PackageManager/ProgressStrings.rs
  • src/install/PackageManager/UpdateRequest.rs
  • src/install/PackageManager/WorkspacePackageJSONCache.rs
  • src/install/PackageManager/install_with_manager.rs
  • src/install/PackageManager/patchPackage.rs
  • src/install/PackageManager/processDependencyList.rs
  • src/install/PackageManager/runTasks.rs
  • src/install/PackageManager/security_scanner.rs
  • src/install/PackageManager/updatePackageJSONAndInstall.rs
  • src/install/PackageManagerTask.rs
💤 Files with no reviewable changes (17)
  • src/css/rules/document.rs
  • src/ast/op.rs
  • src/bundler/linker_context/generateCompileResultForCssChunk.rs
  • src/css/rules/nesting.rs
  • src/css/values/gradient.rs
  • src/css/values/ratio.rs
  • src/bun_core/tty.rs
  • src/css/error.rs
  • src/css/rules/starting_style.rs
  • src/bundler/IndexStringMap.rs
  • src/css/small_list.rs
  • src/crash_handler/CPUFeatures.rs
  • src/bun.js.rs
  • src/css/properties/overflow.rs
  • src/bun_alloc/fallback/z.rs
  • src/css/values/angle.rs
  • src/css/values/time.rs

Comment thread src/analytics/lib.rs
Comment thread src/ast/lib.rs Outdated
Comment thread src/ast/new_store.rs
Comment thread src/brotli/lib.rs
Comment thread src/bun_core/output.rs Outdated
Comment on lines +1517 to +1518
// Unlike Zig, there is no per-scope `[4096]u8` buffered writer; logs route
// through `scoped_writer()` directly (debug-logging perf only).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Serialize writes across scoped loggers.

After removing the per-scope buffer, self.lock only protects one ScopedLogger, but every scope still writes through the same SCOPED_FILE_WRITER. write_fmt can issue multiple write_str calls, so two scopes logging at once can interleave bytes and garble a single line. Add a shared sink lock around write_fmt/flush, or restore per-scope buffering.

🔒 Minimal fix
 pub mod scoped_debug_writer {
     use super::*;
 
+    pub(crate) static WRITE_LOCK: Mutex<()> = Mutex::new(());
     pub(crate) static SCOPED_FILE_WRITER: crate::RacyCell<QuietWriter> =
         crate::RacyCell::new(QuietWriter::ZEROED);
         let _lock = self.lock.lock();
+        let _sink_lock = scoped_debug_writer::WRITE_LOCK.lock();
 
         let mut out = scoped_writer();
         let result = out.write_fmt(args);

Also applies to: 1606-1621

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bun_core/output.rs` around lines 1517 - 1518, The issue is that removing
per-scope buffers means self.lock only serializes access to a single
ScopedLogger while all scopes call into the shared SCOPED_FILE_WRITER, allowing
concurrent write_fmt/write_str sequences to interleave; fix by introducing a
shared sink lock around the shared writer so write_fmt and flush are performed
atomically (e.g., add a global Mutex or RwLock used by SCOPED_FILE_WRITER and
acquire it in ScopedLogger::write_fmt/flush before calling into the writer), or
alternatively restore a per-scope buffer in ScopedLogger and emit a single write
to SCOPED_FILE_WRITER, ensuring no partial write_str calls are issued to the
shared sink.

Comment on lines +1651 to +1653
// Concrete `impl_::Selectors` only — `sel.debug()` formatting requires the
// concrete `Display` impl on `SelectorDebugFmt` (see the comment there).
impl<'a> fmt::Display for SelectorListDebugFmt<'a, impl_::Selectors> {

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/css/selectors/parser.rs"

# Sanity: confirm file exists and print nearby line ranges.
ls -l "$FILE"

# Show the exact code around the referenced lines.
sed -n '1620,1710p' "$FILE" | cat -n
echo "----"

sed -n '1900,2005p' "$FILE" | cat -n
echo "----"

# Locate parse_attribute_selector and show its implementation vicinity.
rg -n "fn parse_attribute_selector" "$FILE"
# Print a window around it.
python3 - <<'PY'
import re, pathlib
p=pathlib.Path("src/css/selectors/parser.rs")
txt=p.read_text()
m=re.search(r"fn parse_attribute_selector[^{]*\{", txt)
if not m:
    print("parse_attribute_selector not found")
    raise SystemExit(1)
start=txt.rfind("\n",0,m.start())
# Convert char index to line number
line_count=txt[:m.start()].count("\n")+1
print("parse_attribute_selector starts at line", line_count)
PY
# print around estimated start
python3 - <<'PY'
import re, pathlib
p=pathlib.Path("src/css/selectors/parser.rs")
txt=p.read_text().splitlines()
for i,line in enumerate(txt,1):
    if "fn parse_attribute_selector" in line:
        start=max(1,i-80)
        end=min(len(txt),i+220)
        for j in range(start,end+1):
            print(f"{j}: {txt[j-1]}")
        break
PY
echo "----"

# Locate SelectorDebugFmt::fmt and print its body.
rg -n "struct SelectorDebugFmt|impl.*SelectorDebugFmt|SelectorDebugFmt::fmt|fn fmt\(" "$FILE"
rg -n "SelectorDebugFmt" "$FILE"
python3 - <<'PY'
import pathlib
p=pathlib.Path("src/css/selectors/parser.rs")
lines=p.read_text().splitlines()
# Find impl fmt::Display for SelectorDebugFmt and print around it.
for i,l in enumerate(lines,1):
    if "impl" in l and "fmt::Display for SelectorDebugFmt" in l:
        start=max(1,i-20); end=min(len(lines),i+120)
        for j in range(start,end+1):
            print(f"{j}: {lines[j-1]}")
        break
PY
echo "----"

# Search for the lowercasing logic referencing local_name_lower[first_uppercase..]
rg -n "local_name_lower|first_uppercase" "$FILE"
echo "----"

# Check that debug wrappers still exist for GenericSelector/GenericSelectorList.
rg -n "impl_::|GenericSelector.*debug|GenericSelectorList.*debug|fn debug\\(" "$FILE"
# Print around generic debug methods if present.
python3 - <<'PY'
import pathlib, re
p=pathlib.Path("src/css/selectors/parser.rs")
lines=p.read_text().splitlines()
targets=[]
for i,l in enumerate(lines,1):
    if re.search(r"fn debug\(", l) and ("GenericSelector" in l or "GenericSelectorList" in l):
        targets.append(i)
for t in targets:
    start=max(1,t-40); end=min(len(lines),t+120)
    print("---- debug around", t)
    for j in range(start,end+1):
        print(f"{j}: {lines[j-1]}")
PY

Repository: oven-sh/bun

Length of output: 34098


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/css/selectors/parser.rs"

# Find arena_lowercase definition / uses.
rg -n "fn arena_lowercase|arena_lowercase\\(" src/css/selectors/parser.rs src/css -S

# If not in this file, search repo-wide for the helper definition.
rg -n "fn arena_lowercase" src -S

# Show the function body with a tight window around its definition.
def_file="$(rg -n "fn arena_lowercase" src -S --files-with-matches | head -n 1)"
echo "arena_lowercase defined in: $def_file"
rg -n "fn arena_lowercase" "$def_file" -n
start=$(rg -n "fn arena_lowercase" "$def_file" | head -n 1 | cut -d: -f1)
# print ~60 lines around it
sed -n "$((start-10)),$((start+120))p" "$def_file" | cat -n

# Also show any comment mentioning Zig DebugFmt.format for SelectorDebugFmt.
rg -n "DebugFmt\\.format|Mirrors the Zig|Zig `DebugFmt|Selector\\(" "$FILE" -S

Repository: oven-sh/bun

Length of output: 7383


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/css/selectors/parser.rs"

# Locate to_case_sensitivity definition and the types involved.
rg -n "to_case_sensitivity\\(" "$FILE"
# Show surrounding code for the callsites already present plus definition(s) elsewhere in module.
rg -n "fn to_case_sensitivity" -S src/css/selectors -g'*.rs'

# Inspect attrs::ParsedCaseSensitivity type / usage.
rg -n "ParsedCaseSensitivity" src/css/selectors -S

# Inspect how Ident compares for case sensitivity: find Ident struct and its eql/hash behavior.
rg -n "struct Ident\\b" -S src/css/selectors src/css -g'*.rs'
rg -n "LocalName|local_name_lower" -S src/css/selectors/parser.rs src/css/selectors -g'*.rs'

# Narrow to attrs matching/equality: find where ParsedCaseSensitivity is consumed.
rg -n "case_sensitivity" src/css/selectors -S

# Show the exact match/impl for SelectorListDebugFmt / SelectorDebugFmt again with more context (avoid diff).
sed -n '1600,1705p' "$FILE" | cat -n
sed -n '1900,2005p' "$FILE" | cat -n

Repository: oven-sh/bun

Length of output: 3824


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/css/selectors/parser.rs"

# Show ParsedCaseSensitivity enum + AttributeFlags->case conversion.
sed -n '380,520p' "$FILE" | cat -n
echo "----"
sed -n '4180,4320p' "$FILE" | cat -n
echo "----"

# Inspect AttributeFlags / parse_attribute_flags bits around the local lines.
rg -n "struct AttributeFlags|enum AttributeFlags|fn parse_attribute_flags" "$FILE"
rg -n "parse_attribute_flags" "$FILE"
sed -n '3960,4300p' "$FILE" | cat -n | head -n 220
echo "----"

# Inspect selector.rs for how ParsedCaseSensitivity is used during matching/serialization.
SEL="src/css/selectors/selector.rs"
rg -n "ParsedCaseSensitivity::|case_sensitive" "$SEL"
# Print around the earlier uses (roughly 260-380, 850-920, and 1580-1630).
sed -n '260,380p' "$SEL" | cat -n
echo "----"
sed -n '840,940p' "$SEL" | cat -n
echo "----"
sed -n '1560,1640p' "$SEL" | cat -n

Repository: oven-sh/bun

Length of output: 38649


Fix selector debug Display/debug() contract mismatch and repair SelectorDebugFmt output.

  • GenericSelectorList<Impl>::debug() / GenericSelector<Impl>::debug() are generic over Impl: BunSelectorImpl, but fmt::Display exists only for SelectorListDebugFmt<'a, impl_::Selectors> / SelectorDebugFmt<'a, impl_::Selectors>; format!("{}", sel.debug()) won’t compile for other Impl. Restrict debug() to impl_::Selectors (or make Display available for all relevant Impls).
  • SelectorDebugFmt::fmt writes Selector( but never writes the closing ), and the Err branch uses writeln! (newline). Add the closing ) in both branches and switch the error branch to write! so diagnostics are well-formed.
  • The parse_attribute_selector lowercasing logic is fine: first_uppercase is the first ASCII A..=Z byte, so bytes before it contain no uppercase; lowercasing only &local_name[first_uppercase..] still produces a fully lowercase cache for local_name_lower.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/selectors/parser.rs` around lines 1651 - 1653, The debug/display
mismatch: make the debug() helpers only available for the concrete
impl_::Selectors and fix SelectorDebugFmt output; specifically, move or
reimplement GenericSelectorList<Impl>::debug() and
GenericSelector<Impl>::debug() as impl blocks for
GenericSelectorList<impl_::Selectors> and GenericSelector<impl_::Selectors> (so
they return SelectorListDebugFmt/SelectorDebugFmt that match the existing
fmt::Display impl for SelectorListDebugFmt<'a, impl_::Selectors> and
SelectorDebugFmt<'a, impl_::Selectors>), and in SelectorDebugFmt::fmt ensure
both branches write the closing ')' (append ')' in the Ok branch and in the Err
branch) and replace the Err branch's writeln! with write! so no stray newline is
emitted; leave parse_attribute_selector logic as-is.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate parity: the reference DebugFmt (selectors/parser.zig:1620-1639) also writes "Selector(" with no closing paren and prints the error arm with a trailing newline; this is a debug-builds-only formatter and the output shape is kept identical. The generic debug() returning a wrapper whose Display is only implemented for the concrete impl_::Selectors compiles fine and matches how the original is used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner, got it — thanks for the context. I’ll retract this finding: keeping the unterminated Selector( / newline error arm for Zig DebugFmt parity is intentional, and the concrete-only Display impl for the generic debug wrapper is fine given the actual impl_::Selectors usage.

🐇💭


✏️ Learnings added
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31783
File: src/css/selectors/parser.rs:1631-1633
Timestamp: 2026-06-05T01:49:57.190Z
Learning: In oven-sh/bun `src/css/selectors/parser.rs`, do not flag `SelectorDebugFmt` / `SelectorListDebugFmt` debug output for missing a closing `)` or using a trailing newline in the error arm. This intentionally matches the reference `selectors/parser.zig` `DebugFmt` behavior and is debug-build-only parity. Also, do not flag generic `GenericSelectorList<Impl>::debug()` / `GenericSelector<Impl>::debug()` returning wrappers whose `fmt::Display` is implemented only for `impl_::Selectors`; this compiles for the intended concrete usage and matches how the original is used.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/resolve_builtins/HardcodedModule.rs:179-186
Timestamp: 2026-06-04T23:12:16.599Z
Learning: In oven-sh/bun (`src/resolve_builtins/HardcodedModule.rs`), the `#[strum(serialize = "...")]` attribute on `HardcodedModule` enum variants defines the `ResolvedSourceTag` identifier (e.g., `"internal:repl"` with a colon), which matches `generated_resolved_source_tag.rs`. The `HardcodedModule::MAP` and `BUN_EXTRA_ALIAS_KVS` entries independently use the slash-form require specifiers (e.g., `b"internal/repl"`). The two forms serve distinct purposes and there is no code path that round-trips a `ResolvedSourceTag` name back into a require specifier for these debug-only (`--expose-internals` / debug build) entries. Do NOT flag the colon-vs-slash mismatch between strum serializations and MAP/Alias keys for `NodeInternalRepl`, `NodeInternalReplAwait`, `NodeInternalReplHistory`, and `NodeInternalUtilInspect` as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to src/**/*.zig : `.zig` files alongside `.rs` files are original Zig implementations kept only as porting references - new code goes in `.rs`, never add new behavior to `.zig` files

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

Comment on lines +1964 to 1968
write!(f, "Selector(")?;
match result {
Ok(()) => write!(f, "{}", bstr::BStr::new(&buf)),
Err(e) => writeln!(f, "<error writing selector: {}>", e.name()),
}

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the exact fmt block near the referenced lines
sed -n '1930,2020p' src/css/selectors/parser.rs | nl -ba | sed -n '1,140p'

Repository: oven-sh/bun

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1930,2020p' src/css/selectors/parser.rs | nl -ba

Repository: oven-sh/bun

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1930,2020p' src/css/selectors/parser.rs | nl -ba

Repository: oven-sh/bun

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show formatter code around the referenced lines with line numbers (without `nl`)
awk 'NR>=1930 && NR<=2020 { printf "%5d\t%s\n", NR, $0 }' src/css/selectors/parser.rs

Repository: oven-sh/bun

Length of output: 4358


Fix SelectorDebugFmt to close ) in both match arms

  • The formatter writes Selector( but neither the Ok nor Err branch outputs the closing ).
  • The Err branch uses writeln!, injecting a newline into what should be a single-line diagnostic.
🩹 Proposed fix
         write!(f, "Selector(")?;
         match result {
-            Ok(()) => write!(f, "{}", bstr::BStr::new(&buf)),
-            Err(e) => writeln!(f, "<error writing selector: {}>", e.name()),
+            Ok(()) => write!(f, "{})", bstr::BStr::new(&buf)),
+            Err(e) => write!(f, "<error writing selector: {}>)", e.name()),
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
write!(f, "Selector(")?;
match result {
Ok(()) => write!(f, "{}", bstr::BStr::new(&buf)),
Err(e) => writeln!(f, "<error writing selector: {}>", e.name()),
}
write!(f, "Selector(")?;
match result {
Ok(()) => write!(f, "{})", bstr::BStr::new(&buf)),
Err(e) => write!(f, "<error writing selector: {}>)", e.name()),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/selectors/parser.rs` around lines 1964 - 1968, The SelectorDebugFmt
implementation writes the opening "Selector(" but never emits the closing ")"
and the Err branch wrongly uses writeln! (inserting a newline); update the match
arms in the Debug impl for SelectorDebugFmt so both arms call write! (not
writeln!) and append the closing parenthesis—i.e., for Ok(()) write the buffer
then write!(")") (or include the ")" in the same write! that writes the buffer)
and for Err(e) write the error message without a newline and then write!(")") so
the formatted output is a single line like Selector(<content>) in both success
and error cases.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the thread above: the missing ) and the newline in the error arm are byte-for-byte the reference DebugFmt's output (selectors/parser.zig:1625,1637); keeping it identical is intentional.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner got it — thanks for the pointer. I’ll treat the missing ) and the newline in the error arm as intentional byte-for-byte parity with the Zig DebugFmt, not a formatting bug.

૮₍ • ᴥ • ₎ა


✏️ Learnings added
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31783
File: src/css/selectors/parser.rs:1942-1946
Timestamp: 2026-06-05T01:49:55.688Z
Learning: In oven-sh/bun `src/css/selectors/parser.rs`, the `fmt::Display` implementation for `SelectorDebugFmt<'a, impl_::Selectors>` intentionally matches the reference `selectors/parser.zig` `DebugFmt` output byte-for-byte: the formatted `Selector(` output does not append a closing `)`, and the error arm uses newline-emitting behavior equivalent to the Zig reference around `selectors/parser.zig` lines 1625 and 1637. Do not flag the missing closing parenthesis or newline in the error branch as a bug.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to src/**/*.zig : `.zig` files alongside `.rs` files are original Zig implementations kept only as porting references - new code goes in `.rs`, never add new behavior to `.zig` files

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

Comment thread src/css/values/color.rs
Comment on lines 1507 to 1520
pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug;
// Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug;
// mirrored as Lab variant for behavioral parity.
LABColor::Lab(LAB { l, a, b, alpha })
}

pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Lab(LAB { l, a, b, alpha })
}

pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Lab(LAB { l, a, b, alpha })

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the correct LABColor variant from these constructors.

These helpers currently all construct LABColor::Lab, so new_oklab, new_lch, and new_oklch erase the authored colorspace immediately and will serialize/behave as the wrong variant.

Suggested fix
     pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
-        LABColor::Lab(LAB { l, a, b, alpha })
+        LABColor::Oklab(OKLAB { l, a, b, alpha })
     }

     pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
-        LABColor::Lab(LAB { l, a, b, alpha })
+        LABColor::Lch(LCH { l, c: a, h: b, alpha })
     }

     pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
-        LABColor::Lab(LAB { l, a, b, alpha })
+        LABColor::Oklch(OKLCH { l, c: a, h: b, alpha })
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug;
// Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug;
// mirrored as Lab variant for behavioral parity.
LABColor::Lab(LAB { l, a, b, alpha })
}
pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Lab(LAB { l, a, b, alpha })
}
pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Lab(LAB { l, a, b, alpha })
pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug;
// mirrored as Lab variant for behavioral parity.
LABColor::Oklab(OKLAB { l, a, b, alpha })
}
pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Lch(LCH { l, c: a, h: b, alpha })
}
pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor {
// Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored.
LABColor::Oklch(OKLCH { l, c: a, h: b, alpha })
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/values/color.rs` around lines 1507 - 1520, Construct and return the
specific enum variants instead of always using LABColor::Lab: update new_oklab
to return LABColor::OkLab(OKLAB { l, a, b, alpha }), update new_lch to return
LABColor::Lch(LCH { l, a, b, alpha }), and update new_oklch to return
LABColor::OkLch(OKLCH { l, a, b, alpha }) so each constructor preserves its
intended colorspace variant (modify the variant/struct names to match the exact
identifiers used in the file if they differ).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate parity: the reference newLCH/newOKLCH return .lab as well (css/values/color.zig:1453-1463), and the Rust code carries an explicit '(sic) — kept for behavioral compatibility' note. Changing the variant would diverge from the shipped behavior; if it's a real bug it should be fixed in its own change with CSS color tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner, you’re right — I missed that this is intentional parity with the Zig reference and that the Rust comments already document the (sic) behavior. I’ll treat this as a false positive for this PR; changing these constructors would be a behavior change and belongs in a separate, test-backed CSS color fix if pursued.

🐇💭


✏️ Learnings added
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31783
File: src/css/values/color.rs:1494-1507
Timestamp: 2026-06-05T01:50:05.381Z
Learning: In oven-sh/bun `src/css/values/color.rs`, do not flag `LABColor::new_oklab`, `LABColor::new_lch`, or `LABColor::new_oklch` returning `LABColor::Lab(...)` as a bug in port/TODO-audit reviews. This is deliberate behavioral parity with the Zig reference `css/values/color.zig` where `newLCH`/`newOKLCH` also return `.lab`, and the Rust code documents it with `(sic) — kept for behavioral compatibility`. Any semantic correction should be a separate change with CSS color tests.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to src/**/*.zig : `.zig` files alongside `.rs` files are original Zig implementations kept only as porting references - new code goes in `.rs`, never add new behavior to `.zig` files

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

Comment thread src/css/values/color.rs
Comment on lines 2497 to 2503
ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => {
first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
}
// PORT NOTE: Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity.
// Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity.
ColorSpaceName::XyzD50 => {
first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
}

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mix xyz-d50 colors in XYZd50, not XYZd65.

ColorSpaceName::XyzD50 currently dispatches to interpolate::<XYZd65>, so color-mix(in xyz-d50, …) runs in the wrong colorspace.

Suggested fix
         ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => {
             first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
         }
         ColorSpaceName::XyzD50 => {
-            first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
+            first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => {
first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
}
// PORT NOTE: Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity.
// Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity.
ColorSpaceName::XyzD50 => {
first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
}
ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => {
first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
}
// Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity.
ColorSpaceName::XyzD50 => {
first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/values/color.rs` around lines 2497 - 2503, The match arm for
ColorSpaceName::XyzD50 dispatches to interpolate::<XYZd65>, causing xyz-d50
mixes to run in the wrong colorspace; update that arm to call
first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method) (replace
XYZd65 with XYZd50) so ColorSpaceName::XyzD50 uses the correct XYZd50
implementation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate parity: the reference also interpolates xyz-d50 as XYZd65 (css/values/color.zig:3115: ."xyz-d50" => interpolate XYZd65), and the code is annotated '(sic) — kept for behavioral compatibility'. A correctness fix should be a separate change with tests rather than a silent behavioral diff inside this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner, agreed — thanks for the clarification. I’ll withdraw this as a finding for this PR. Since the Rust port is intentionally preserving the Zig behavior and the code is annotated as parity-preserving, changing xyz-d50 to interpolate through XYZd50 would be a separate behavioral fix and should come with targeted tests.

I’ll remember not to flag this parity case in future reviews.

🐇💭


✏️ Learnings added
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31783
File: src/css/values/color.rs:2481-2487
Timestamp: 2026-06-05T01:50:08.214Z
Learning: In oven-sh/bun `src/css/values/color.rs`, `parse_color_mix` intentionally maps `ColorSpaceName::XyzD50` to `first_color.interpolate::<XYZd65>(...)` for parity with the Zig reference (`css/values/color.zig`, where `"xyz-d50"` also interpolates as `XYZd65`). Do not flag this as a bug in TODO/PORT-note audit or parity-preserving PRs; any correctness change to interpolate `xyz-d50` through `XYZd50` should be treated as a separate behavioral change with tests.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

Comment thread src/errno/lib.rs Outdated
The codebase has moved from a port to a standalone Rust implementation;
comments citing the prior Zig implementation (file/line cross-references,
std.* stdlib mentions, quoted Zig syntax, divergence notes) no longer aid
maintenance. Delete them, keeping SAFETY justifications rewritten in terms
of the Rust code's own invariants. References to current identifiers
(ZigString etc.) are unchanged.
NewServer::init moves the config into the server, so the hot-map
registration that runs after init read an empty id from the stale local
and registered every server under the same key, panicking on the second
Bun.serve in --hot processes. Read allow_hot and id from the server's own
config instead. This path was unreachable until hot_map() gained its
lazy-init accessor.

Also update parsePackedFeaturesList() for the new define_features! shape
(internal @storage recursion and core = IDENT aliases).
@Jarred-Sumner Jarred-Sumner requested a review from alii as a code owner June 4, 2026 01:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun_core/util.rs (1)

4871-4888: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve posix_spawnp/execvp PATH lookup behavior in the Linux/FreeBSD fallback (src/bun_core/util.rs, ~4871–4888).

The code falls back to getenv_z(...).unwrap_or(b"") and then which() skips empty PATH components (if dir.is_empty() { continue; }). That means:

  • Unset PATH becomes “no search directories” → always ENOENT (instead of a typical system default search path used by many libc implementations).
  • PATH values like :/bin or foo::bar do not probe the current directory (empty components are not treated like .).

Because the implementation claims a posix_spawnp-like contract, update the behavior to match common execvp semantics: treat empty PATH components as the current directory, and handle unset PATH with the platform’s default search path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bun_core/util.rs` around lines 4871 - 4888, The PATH lookup in the
Linux/FreeBSD posix_spawn_bun fallback currently calls
getenv_z(...).unwrap_or(b"") and relies on which(...), but that causes empty
PATH (unset) or empty components to be treated as “no search” — break the
posix_spawnp/execvp contract. Change the logic so that when getenv_z returns
None (unset PATH) you substitute the platform’s default search path (e.g. the
same default used by execvp on the platform, typically "/bin:/usr/bin"), and
ensure which(...) treats empty path components as the current directory (i.e.
interpret "" as ".") instead of skipping them; update the code around argv[0],
getenv_z(...) and the call to which(...) (and/or which's handling) to implement
these two behaviors so bare program names are resolved like execvp/posix_spawnp.
♻️ Duplicate comments (2)
src/brotli/lib.rs (1)

419-433: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The flush() implementation should use full_flush_op to drain pending encoder state.

As noted in a previous review, calling self.compressor.write(b"", false) routes through the stream's configured flush_op, which may be a no-op for PROCESS-mode streams (per the inline comment). To honor the bun_io::Write::flush() contract — ensuring all buffered compressed bytes reach the sink before returning — the encoder should run with full_flush_op instead.

🔧 Proposed fix
 fn flush(&mut self) -> Result<(), Error> {
-    let out = self.compressor.write(b"", false)?;
+    let op = self.compressor.full_flush_op;
+    let result = BrotliEncoder::compress_stream(
+        unsafe { &mut *self.compressor.brotli },
+        op,
+        b""
+    );
+    if !result.success {
+        self.compressor.state = CompressionState::Error;
+        return Err(err!("BrotliCompressionError"));
+    }
+    let out = result.output;
     self.input_writer.write_all(out)?;
     self.input_writer.flush()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/brotli/lib.rs` around lines 419 - 433, The flush() implementation on
BrotliWriter currently calls self.compressor.write(b"", false) which uses the
stream's configured flush_op and can be a no-op; change it to invoke the
compressor with the full_flush_op (e.g. self.compressor.write(b"",
full_flush_op())) so the encoder emits all pending compressed bytes before
returning, then keep the existing calls to self.input_writer.write_all(out)? and
self.input_writer.flush() to forward and flush the sink; update the call site in
the BrotliWriter::flush method to use full_flush_op and handle the returned
output/errors the same way.
src/analytics/lib.rs (1)

117-120: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the lint allow from the aliasing pub use arm.

Line 119 still applies #[allow(non_upper_case_globals)] to a use item, so Clippy reports it as a useless attribute and the current pipeline is failing on this line. The allow should stay only on the real pub static arm.

Suggested fix
         (`@storage` $(#[$doc:meta])* $ident:ident, $core:ident) => {
             $(#[$doc])*
-            #[allow(non_upper_case_globals)]
             pub use ::bun_core::Global::features::$core as $ident;
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/analytics/lib.rs` around lines 117 - 120, In the macro arm matching
"(`@storage` $(#[$doc:meta])* $ident:ident, $core:ident) => { ... }" remove the
#[allow(non_upper_case_globals)] attribute that sits immediately before the `pub
use ::bun_core::Global::features::$core as $ident;` alias, leaving the doc
attribute `$(#[$doc])*` intact; ensure the #[allow(non_upper_case_globals)]
remains only on the other macro arm that expands to a real `pub static` so
Clippy no longer flags the `use` alias as having a useless attribute.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/boringssl_sys/boringssl.rs`:
- Around line 442-443: The sk_GENERAL_NAME_free_func is incorrectly typed to
accept a stack pointer; change its signature to unsafe extern "C" fn(*mut
GENERAL_NAME) so the per-element destructor is used, update the trampoline
function sk_GENERAL_NAME_call_free_func to cast and forward a single *mut
GENERAL_NAME to the callback (not *mut struct_stack_st_GENERAL_NAME), and update
the call site (sk_GENERAL_NAME_pop_free and any invocation like
boring::sk_GENERAL_NAME_pop_free(n, boring::sk_GENERAL_NAME_free)) to pass the
per-element free function (sk_GENERAL_NAME_free should be an element destructor
taking *mut GENERAL_NAME) instead of a stack deallocator to avoid passing a
stack pointer into the element-free trampoline.

In `@src/bun_core/util.rs`:
- Around line 2150-2151: ThreadLock's use of Cell<StoredTrace> for locked_at
races with the atomic owning_thread because lock() publishes ownership
(owning_thread.swap) before writing locked_at.set(...) and unlock() releases
ownership before clearing locked_at.set(EMPTY), so the atomic does not
synchronize Cell access; fix by making locked_at a properly synchronized
atomic-backed storage (e.g., change Cell<StoredTrace> to an atomic
representation like AtomicUsize/AtomicPtr or a Mutex/AtomicU64 depending on
StoredTrace size) or by reordering and inserting the proper atomic fences so
that locked_at is fully written before publishing owning_thread in lock() and
cleared only after owning_thread is cleared in unlock(); update ThreadLock,
lock(), unlock(), and any StoredTrace conversions accordingly to ensure
atomic/ordered reads and writes.

---

Outside diff comments:
In `@src/bun_core/util.rs`:
- Around line 4871-4888: The PATH lookup in the Linux/FreeBSD posix_spawn_bun
fallback currently calls getenv_z(...).unwrap_or(b"") and relies on which(...),
but that causes empty PATH (unset) or empty components to be treated as “no
search” — break the posix_spawnp/execvp contract. Change the logic so that when
getenv_z returns None (unset PATH) you substitute the platform’s default search
path (e.g. the same default used by execvp on the platform, typically
"/bin:/usr/bin"), and ensure which(...) treats empty path components as the
current directory (i.e. interpret "" as ".") instead of skipping them; update
the code around argv[0], getenv_z(...) and the call to which(...) (and/or
which's handling) to implement these two behaviors so bare program names are
resolved like execvp/posix_spawnp.

---

Duplicate comments:
In `@src/analytics/lib.rs`:
- Around line 117-120: In the macro arm matching "(`@storage` $(#[$doc:meta])*
$ident:ident, $core:ident) => { ... }" remove the
#[allow(non_upper_case_globals)] attribute that sits immediately before the `pub
use ::bun_core::Global::features::$core as $ident;` alias, leaving the doc
attribute `$(#[$doc])*` intact; ensure the #[allow(non_upper_case_globals)]
remains only on the other macro arm that expands to a real `pub static` so
Clippy no longer flags the `use` alias as having a useless attribute.

In `@src/brotli/lib.rs`:
- Around line 419-433: The flush() implementation on BrotliWriter currently
calls self.compressor.write(b"", false) which uses the stream's configured
flush_op and can be a no-op; change it to invoke the compressor with the
full_flush_op (e.g. self.compressor.write(b"", full_flush_op())) so the encoder
emits all pending compressed bytes before returning, then keep the existing
calls to self.input_writer.write_all(out)? and self.input_writer.flush() to
forward and flush the sink; update the call site in the BrotliWriter::flush
method to use full_flush_op and handle the returned output/errors the same way.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7939792-4ee1-467a-be2e-bbcb5224af5a

📥 Commits

Reviewing files that changed from the base of the PR and between b43c712 and 8822df3.

📒 Files selected for processing (297)
  • scripts/build/features-json.ts
  • src/analytics/lib.rs
  • src/analytics/schema.rs
  • src/api/lib.rs
  • src/ast/ast_memory_allocator.rs
  • src/ast/ast_result.rs
  • src/ast/b.rs
  • src/ast/base.rs
  • src/ast/binding.rs
  • src/ast/char_freq.rs
  • src/ast/e.rs
  • src/ast/expr.rs
  • src/ast/fold_string_addition.rs
  • src/ast/g.rs
  • src/ast/import_record.rs
  • src/ast/known_global.rs
  • src/ast/lexer_log.rs
  • src/ast/lexer_tables.rs
  • src/ast/lib.rs
  • src/ast/loader.rs
  • src/ast/new_store.rs
  • src/ast/nodes.rs
  • src/ast/op.rs
  • src/ast/runtime.rs
  • src/ast/s.rs
  • src/ast/scope.rs
  • src/ast/server_component_boundary.rs
  • src/ast/stmt.rs
  • src/ast/symbol.rs
  • src/ast/target.rs
  • src/ast/transpiler_cache.rs
  • src/ast/ts.rs
  • src/ast/use_directive.rs
  • src/ast_jsc/lib.rs
  • src/base64/lib.rs
  • src/boringssl/lib.rs
  • src/boringssl_sys/boringssl.rs
  • src/brotli/lib.rs
  • src/brotli_sys/brotli_c.rs
  • src/bun.js.rs
  • src/bun_alloc/BufferFallbackAllocator.rs
  • src/bun_alloc/MaxHeapAllocator.rs
  • src/bun_alloc/MimallocArena.rs
  • src/bun_alloc/NullableAllocator.rs
  • src/bun_alloc/ast_alloc.rs
  • src/bun_alloc/baby_vec.rs
  • src/bun_alloc/basic.rs
  • src/bun_alloc/fallback.rs
  • src/bun_alloc/fallback/z.rs
  • src/bun_alloc/hashbrown_bridge.rs
  • src/bun_alloc/heap_breakdown.rs
  • src/bun_alloc/lib.rs
  • src/bun_alloc/maybe_owned.rs
  • src/bun_alloc/memory.rs
  • src/bun_alloc/stack_fallback.rs
  • src/bun_bin/lib.rs
  • src/bun_bin/phase_c_exports.rs
  • src/bun_core/Global.rs
  • src/bun_core/Progress.rs
  • src/bun_core/atomic_cell.rs
  • src/bun_core/bounded_array.rs
  • src/bun_core/debug.rs
  • src/bun_core/deprecated.rs
  • src/bun_core/env.rs
  • src/bun_core/env_var.rs
  • src/bun_core/external_shared.rs
  • src/bun_core/feature_flags.rs
  • src/bun_core/fmt.rs
  • src/bun_core/heap.rs
  • src/bun_core/hint.rs
  • src/bun_core/lib.rs
  • src/bun_core/output.rs
  • src/bun_core/result.rs
  • src/bun_core/string/HashedString.rs
  • src/bun_core/string/MutableString.rs
  • src/bun_core/string/SmolStr.rs
  • src/bun_core/string/StringBuilder.rs
  • src/bun_core/string/StringJoiner.rs
  • src/bun_core/string/escapeRegExp.rs
  • src/bun_core/string/identifier.rs
  • src/bun_core/string/immutable.rs
  • src/bun_core/string/immutable/exact_size_matcher.rs
  • src/bun_core/string/immutable/unicode.rs
  • src/bun_core/string/mod.rs
  • src/bun_core/string/write.rs
  • src/bun_core/string/wtf.rs
  • src/bun_core/thread_id.rs
  • src/bun_core/tty.rs
  • src/bun_core/util.rs
  • src/bun_core/windows_sys.rs
  • src/bun_core/wtf.rs
  • src/bun_core_macros/lib.rs
  • src/bun_output_tags/lib.rs
  • src/bundler/AstBuilder.rs
  • src/bundler/BundleThread.rs
  • src/bundler/Chunk.rs
  • src/bundler/DeferredBatchTask.rs
  • src/bundler/Graph.rs
  • src/bundler/HTMLImportManifest.rs
  • src/bundler/HTMLScanner.rs
  • src/bundler/IndexStringMap.rs
  • src/bundler/LinkerContext.rs
  • src/bundler/LinkerGraph.rs
  • src/bundler/OutputFile.rs
  • src/bundler/ParseTask.rs
  • src/bundler/PathToSourceIndexMap.rs
  • src/bundler/ServerComponentParseTask.rs
  • src/bundler/ThreadPool.rs
  • src/bundler/analyze_transpiled_module.rs
  • src/bundler/barrel_imports.rs
  • src/bundler/bundle_v2.rs
  • src/bundler/bundled_ast.rs
  • src/bundler/cache.rs
  • src/bundler/defines.rs
  • src/bundler/entry_points.rs
  • src/bundler/lib.rs
  • src/bundler/linker.rs
  • src/bundler/linker_context/MetafileBuilder.rs
  • src/bundler/linker_context/OutputFileListBuilder.rs
  • src/bundler/linker_context/StaticRouteVisitor.rs
  • src/bundler/linker_context/computeChunks.rs
  • src/bundler/linker_context/computeCrossChunkDependencies.rs
  • src/bundler/linker_context/convertStmtsForChunk.rs
  • src/bundler/linker_context/convertStmtsForChunkForDevServer.rs
  • src/bundler/linker_context/doStep5.rs
  • src/bundler/linker_context/findAllImportedPartsInJSOrder.rs
  • src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs
  • src/bundler/linker_context/findImportedFilesInCSSOrder.rs
  • src/bundler/linker_context/generateChunksInParallel.rs
  • src/bundler/linker_context/generateCodeForFileInChunkJS.rs
  • src/bundler/linker_context/generateCodeForLazyExport.rs
  • src/bundler/linker_context/generateCompileResultForCssChunk.rs
  • src/bundler/linker_context/generateCompileResultForHtmlChunk.rs
  • src/bundler/linker_context/generateCompileResultForJSChunk.rs
  • src/bundler/linker_context/postProcessCSSChunk.rs
  • src/bundler/linker_context/postProcessHTMLChunk.rs
  • src/bundler/linker_context/postProcessJSChunk.rs
  • src/bundler/linker_context/prepareCssAstsForChunk.rs
  • src/bundler/linker_context/renameSymbolsInChunk.rs
  • src/bundler/linker_context/scanImportsAndExports.rs
  • src/bundler/linker_context/writeOutputFilesToDisk.rs
  • src/bundler/options.rs
  • src/bundler/transpiler.rs
  • src/bundler_jsc/PluginRunner.rs
  • src/bundler_jsc/analyze_jsc.rs
  • src/bundler_jsc/lib.rs
  • src/bundler_jsc/options_jsc.rs
  • src/bundler_jsc/source_map_mode_jsc.rs
  • src/bunfig/arguments.rs
  • src/bunfig/bunfig.rs
  • src/bunfig/lib.rs
  • src/c-headers-for-zig.h
  • src/cares_sys/c_ares.rs
  • src/clap/args.rs
  • src/clap/comptime.rs
  • src/clap/lib.rs
  • src/clap/streaming.rs
  • src/clap_macros/lib.rs
  • src/codegen/bindgen-lib-internal.ts
  • src/codegen/bindgen-lib.ts
  • src/codegen/bindgen.ts
  • src/codegen/bindgenv2/internal/base.ts
  • src/codegen/bindgenv2/internal/dictionary.ts
  • src/codegen/bindgenv2/internal/union.ts
  • src/codegen/class-definitions.ts
  • src/codegen/cppbind.ts
  • src/codegen/generate-classes.ts
  • src/codegen/generate-host-exports.ts
  • src/codegen/generate-js2native.ts
  • src/codegen/generate-jssink.ts
  • src/collections/StaticHashMap.rs
  • src/collections/array_hash_map.rs
  • src/collections/array_list.rs
  • src/collections/bit_set.rs
  • src/collections/comptime_string_map.rs
  • src/collections/hive_array.rs
  • src/collections/identity_context.rs
  • src/collections/lib.rs
  • src/collections/linear_fifo.rs
  • src/collections/multi_array_list.rs
  • src/collections/pool.rs
  • src/collections/string_map.rs
  • src/collections/vec_ext.rs
  • src/collections/zig_hash_map.rs
  • src/crash_handler/CPUFeatures.rs
  • src/crash_handler/handle_oom.rs
  • src/crash_handler/lib.rs
  • src/csrf/lib.rs
  • src/css/compat.rs
  • src/css/context.rs
  • src/css/css_modules.rs
  • src/css/css_parser.rs
  • src/css/declaration.rs
  • src/css/dependencies.rs
  • src/css/error.rs
  • src/css/generics.rs
  • src/css/lib.rs
  • src/css/logical.rs
  • src/css/media_query.rs
  • src/css/prefixes.rs
  • src/css/printer.rs
  • src/css/properties/align.rs
  • src/css/properties/animation.rs
  • src/css/properties/background.rs
  • src/css/properties/border.rs
  • src/css/properties/border_image.rs
  • src/css/properties/border_radius.rs
  • src/css/properties/box_shadow.rs
  • src/css/properties/css_modules.rs
  • src/css/properties/custom.rs
  • src/css/properties/display.rs
  • src/css/properties/effects.rs
  • src/css/properties/flex.rs
  • src/css/properties/font.rs
  • src/css/properties/generate_properties.ts
  • src/css/properties/grid.rs
  • src/css/properties/list.rs
  • src/css/properties/margin_padding.rs
  • src/css/properties/masking.rs
  • src/css/properties/mod.rs
  • src/css/properties/outline.rs
  • src/css/properties/overflow.rs
  • src/css/properties/position.rs
  • src/css/properties/prefix_handler.rs
  • src/css/properties/properties_generated.rs
  • src/css/properties/properties_impl.rs
  • src/css/properties/shape.rs
  • src/css/properties/size.rs
  • src/css/properties/svg.rs
  • src/css/properties/text.rs
  • src/css/properties/transform.rs
  • src/css/properties/transition.rs
  • src/css/properties/ui.rs
  • src/css/rules/container.rs
  • src/css/rules/counter_style.rs
  • src/css/rules/custom_media.rs
  • src/css/rules/document.rs
  • src/css/rules/font_face.rs
  • src/css/rules/font_palette_values.rs
  • src/css/rules/import.rs
  • src/css/rules/keyframes.rs
  • src/css/rules/layer.rs
  • src/css/rules/media.rs
  • src/css/rules/mod.rs
  • src/css/rules/namespace.rs
  • src/css/rules/nesting.rs
  • src/css/rules/page.rs
  • src/css/rules/property.rs
  • src/css/rules/scope.rs
  • src/css/rules/starting_style.rs
  • src/css/rules/style.rs
  • src/css/rules/supports.rs
  • src/css/rules/tailwind.rs
  • src/css/rules/unknown.rs
  • src/css/rules/viewport.rs
  • src/css/selectors/builder.rs
  • src/css/selectors/mod.rs
  • src/css/selectors/parser.rs
  • src/css/selectors/selector.rs
  • src/css/small_list.rs
  • src/css/sourcemap.rs
  • src/css/targets.rs
  • src/css/values/alpha.rs
  • src/css/values/angle.rs
  • src/css/values/calc.rs
  • src/css/values/color.rs
  • src/css/values/color_generated.rs
  • src/css/values/css_string.rs
  • src/css/values/easing.rs
  • src/css/values/gradient.rs
  • src/css/values/ident.rs
  • src/css/values/image.rs
  • src/css/values/length.rs
  • src/css/values/mod.rs
  • src/css/values/number.rs
  • src/css/values/percentage.rs
  • src/css/values/position.rs
  • src/css/values/ratio.rs
  • src/css/values/rect.rs
  • src/css/values/resolution.rs
  • src/css/values/size.rs
  • src/css/values/syntax.rs
  • src/css/values/time.rs
  • src/css/values/url.rs
  • src/css_derive/lib.rs
  • src/css_jsc/color_js.rs
  • src/css_jsc/css_internals.rs
  • src/css_jsc/error_jsc.rs
  • src/dispatch/lib.rs
  • src/dns/lib.rs
  • src/dotenv/env_loader.rs
  • src/errno/darwin_errno.rs
  • src/errno/freebsd_errno.rs
  • src/errno/lib.rs
  • src/errno/linux_errno.rs
  • src/errno/windows_errno.rs
  • src/event_loop/AnyEventLoop.rs
💤 Files with no reviewable changes (6)
  • src/bun_core/string/escapeRegExp.rs
  • src/ast/s.rs
  • src/bun_core/feature_flags.rs
  • src/ast_jsc/lib.rs
  • src/bun_alloc/basic.rs
  • src/bun_alloc/memory.rs

Comment on lines +442 to 443
/// Per-stack free callback type used by `sk_GENERAL_NAME_pop_free`.
pub(crate) type sk_GENERAL_NAME_free_func = unsafe extern "C" fn(*mut struct_stack_st_GENERAL_NAME);

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Local Rust bindings/callers =="
rg -n "sk_GENERAL_NAME_(free_func|call_free_func|pop_free|free)|GENERAL_NAME_free" \
  src/boringssl_sys/boringssl.rs src/boringssl/lib.rs

echo
echo "== BoringSSL headers =="
fd -p 'x509v3.h' vendor/boringssl/include/openssl | while read -r f; do
  echo "-- $f --"
  rg -n "GENERAL_NAME|GENERAL_NAME_free|sk_.*GENERAL_NAME|STACK_OF\\(GENERAL_NAME\\)" "$f"
done

fd -p 'stack.h' vendor/boringssl/include/openssl | while read -r f; do
  echo "-- $f --"
  rg -n "sk_pop_free_ex|OPENSSL_sk_pop_free_ex|OPENSSL_sk_free_func|OPENSSL_sk_call_free_func" "$f"
done

Repository: oven-sh/bun

Length of output: 1687


Use an element destructor here, not a stack destructor.

sk_GENERAL_NAME_pop_free calls free_func once per GENERAL_NAME element, but sk_GENERAL_NAME_free_func/sk_GENERAL_NAME_free are typed as unsafe extern "C" fn(*mut struct_stack_st_GENERAL_NAME) and sk_GENERAL_NAME_call_free_func reinstates that stack type before invoking the callback. As a result, src/boringssl/lib.rs’s boring::sk_GENERAL_NAME_pop_free(n, boring::sk_GENERAL_NAME_free) routes a stack deallocator through the per-element free trampoline, risking heap corruption while parsing peer certificates.

Change the callback type to take *mut GENERAL_NAME and update the trampoline + call site to pass the per-element GENERAL_NAME free binding (not sk_GENERAL_NAME_free).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/boringssl_sys/boringssl.rs` around lines 442 - 443, The
sk_GENERAL_NAME_free_func is incorrectly typed to accept a stack pointer; change
its signature to unsafe extern "C" fn(*mut GENERAL_NAME) so the per-element
destructor is used, update the trampoline function
sk_GENERAL_NAME_call_free_func to cast and forward a single *mut GENERAL_NAME to
the callback (not *mut struct_stack_st_GENERAL_NAME), and update the call site
(sk_GENERAL_NAME_pop_free and any invocation like
boring::sk_GENERAL_NAME_pop_free(n, boring::sk_GENERAL_NAME_free)) to pass the
per-element free function (sk_GENERAL_NAME_free should be an element destructor
taking *mut GENERAL_NAME) instead of a stack deallocator to avoid passing a
stack pointer into the element-free trampoline.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the type confusion, but this is faithfully-ported pre-existing behavior, not introduced here: the reference does the identical thing (boringssl.zig:180,202 pass sk_GENERAL_NAME_free to sk_GENERAL_NAME_pop_free). Worth investigating separately — the correct per-element destructor per BoringSSL's DEFINE_STACK_OF conventions is GENERAL_NAME_free — but changing free semantics on the SAN path deserves its own PR with targeted TLS tests, not a drive-by inside this cleanup.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner, understood — the Zig reference (boringssl.zig:180,202) has the same pattern, so this is a faithful port of pre-existing behavior, not a regression introduced here. The correct fix (GENERAL_NAME_free as the per-element destructor) would touch the SAN verification path and really does warrant dedicated TLS coverage before merging.

Would you like me to open a GitHub issue to track this? I can file it with the relevant context (the correct destructor, the affected call site in src/boringssl/lib.rs, and a pointer back to this PR) so it doesn't get lost.


🧠 Learnings used
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to src/**/*.rs : Never let a pointer or slice outlive the memory it points into - audit slices of stack buffers, growable containers across calls, and temporary values

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

Comment thread src/bun_core/util.rs
Comment on lines +2150 to 2151
// unsynchronized write to `locked_at` is fine — if two
// threads race here, the `owning_thread.swap` panic fires first.

@coderabbitai coderabbitai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate ThreadLock definition + relevant methods
rg -n "struct ThreadLock|impl ThreadLock|ThreadLock" src/bun_core/util.rs

# Show the exact lines around the referenced location
sed -n '2100,2300p' src/bun_core/util.rs

# Also show wider context near spawn_sync_inherit_impl for any comment coupling (not required, but helpful)
rg -n "spawn_sync_inherit_impl" src/bun_core/util.rs
sed -n '4700,5050p' src/bun_core/util.rs

Repository: oven-sh/bun

Length of output: 22432


ThreadLock still races on locked_at in debug builds
ThreadLock has unsafe impl Sync while locked_at: Cell<StoredTrace> is non-atomic. In lock(), owning_thread.swap(...AcqRel) publishes ownership before locked_at.set(...), so a contender can observe the non-INVALID owner and read locked_at.get() while the owner is still writing it. In unlock(), owning_thread.store(...Release) happens before locked_at.set(EMPTY), so the next acquirer can overlap with the previous thread clearing locked_at. The atomic word does not synchronize Cell<locked_at> access as the comment claims.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bun_core/util.rs` around lines 2150 - 2151, ThreadLock's use of
Cell<StoredTrace> for locked_at races with the atomic owning_thread because
lock() publishes ownership (owning_thread.swap) before writing
locked_at.set(...) and unlock() releases ownership before clearing
locked_at.set(EMPTY), so the atomic does not synchronize Cell access; fix by
making locked_at a properly synchronized atomic-backed storage (e.g., change
Cell<StoredTrace> to an atomic representation like AtomicUsize/AtomicPtr or a
Mutex/AtomicU64 depending on StoredTrace size) or by reordering and inserting
the proper atomic fences so that locked_at is fully written before publishing
owning_thread in lock() and cleared only after owning_thread is cleared in
unlock(); update ThreadLock, lock(), unlock(), and any StoredTrace conversions
accordingly to ensure atomic/ordered reads and writes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixing here: this matches the Zig original (src/safety/ThreadLock.zig), which has no synchronization at all on either field — the Rust version is already strictly more ordered. ThreadLock is a debug-only single-threaded-access assertion; the racy locked_at read only happens on the already-panicking diagnostic path, where the trace is best-effort. Making it fully synchronized would add cost to every debug-build lock/unlock for a diagnostic that fires once before abort.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jarred-Sumner, understood — thanks for the clarification. I’ll withdraw this finding for this PR. Given ThreadLock is a debug-only single-threaded-access assertion and the locked_at trace is best-effort only on the already-panicking path, matching the Zig semantics here is intentional and I won’t ask for synchronization/reordering.

I’ll remember this for future reviews.

🐇💭


✏️ Learnings added
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31783
File: src/bun_core/util.rs:2150-2151
Timestamp: 2026-06-05T01:49:45.910Z
Learning: In oven-sh/bun `src/bun_core/util.rs`, do not flag `ThreadLock`'s debug-only `locked_at: Cell<StoredTrace>` access as needing full synchronization merely because it can race with `owning_thread`. This intentionally matches `src/safety/ThreadLock.zig` semantics, where the original has no synchronization on either field. `ThreadLock` is a debug-only single-threaded-access assertion; the `locked_at` trace is best-effort diagnostic data read only on the already-panicking path, and adding synchronization would add debug-build lock/unlock cost for a diagnostic that fires once before abort.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: provide Windows equivalent for POSIX-only API additions in the same PR

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31823
File: src/js/node/inspector.ts:106-113
Timestamp: 2026-06-05T01:04:19.604Z
Learning: In oven-sh/bun (PR `#31823`), `Runtime.consoleAPICalled` notifications emitted by the in-process `inspector.Session` intentionally omit `params.stackTrace`. The only upstream test that dereferences `notification.params.stackTrace.callFrames[0]` is `test-inspector-console-top-frame.js`, which is guarded by `common.skipIfInspectorDisabled()`. Because this PR sets `process.features.inspector = false`, that test is always skipped when running under Bun and never exercises the in-process session. Synthesizing CDP-shaped `CallFrame` objects from JS is non-trivial (no reliable `scriptId` mapping), so `stackTrace` is deferred until a real consumer requires it. Do not flag the absence of `params.stackTrace` in the in-process `Runtime.consoleAPICalled` payload as a bug.

Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-05T00:36:36.107Z
Learning: Applies to **/*.rs : Platform-specific code: compile across all targets (linux/macos/windows × x64/aarch64) by running `bun run rust:check-all` when making platform-specific changes

Learnt from: cirospaciari
Repo: oven-sh/bun PR: 31827
File: src/js/internal/repl/await.js:71-86
Timestamp: 2026-06-04T23:12:09.051Z
Learning: In oven-sh/bun PR `#31827`, `src/js/internal/repl/await.js` is a verbatim byte-close port of Node.js v26.3.0 `lib/internal/repl/await.js`. The `registerVariableDeclarationIdentifiers` function inside `processTopLevelAwait` does not handle null elements (array elisions), `RestElement`, or `AssignmentPattern` nodes — but this is an upstream Node.js bug (verified on Node v26.3.0: `processTopLevelAwait("let [,,x] = await a;")` throws "Cannot read properties of null (reading 'type')"). Rest and default cases work correctly in both. Do NOT suggest patching this function in Bun; any fix must go to nodejs/node first. The file is kept byte-close to upstream to enable clean future syncs.

Learnt from: robobun
Repo: oven-sh/bun PR: 30522
File: src/runtime/cli/publish_command.rs:914-916
Timestamp: 2026-05-15T11:58:16.766Z
Learning: In this repo’s Rust code, do not flag a “typed accessor missing” or similar violation when reading `npm_config_*` environment variables. For `npm_config_*` keys (e.g. `NPM_CONFIG_REGISTRY`, `NPM_CONFIG_PROVENANCE`), the accepted pattern is to read them via `bun_core::getenv_z` using both upper-case and lower-case forms combined with `.or_else(...)`. This dual `getenv_z` approach is intentional and consistent even when `src/bun_core/env_var.rs` does not provide typed accessors for specific `npm_config_*` variables (e.g., in `PackageManagerOptions::load` and `publish_command.rs`).

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 30971
File: src/bun_core/thread_id.rs:124-125
Timestamp: 2026-05-19T08:28:57.054Z
Learning: In the oven-sh/bun `bun_core` crate, reviewers should treat bare `#[thread_local]` statics as intentional and correct (e.g., `static TLS_*: core::cell::Cell<ThreadId>`). Do not request replacing them with the `thread_local!` macro based solely on that they are not using `thread_local!`. This pattern is used to match `thread_local`/Zig-style semantics and avoid `LocalKey` initialization-state/destructor-registration overhead. Only flag/reconsider if this file/crate is not actually enabling the `thread_local` nightly feature (i.e., `#![feature(thread_local)]` is missing) or the code materially deviates from the established pattern.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31176
File: src/io/uv_handle.rs:92-100
Timestamp: 2026-05-21T09:34:31.660Z
Learning: In the oven-sh/bun repo, review allocator-mismatch/safety warnings carefully around raw `Box` round-trips. `bun_core::heap::{into_raw, take, destroy, alloc}` are `#[inline(always)]` thin aliases with identical machine code to `Box::into_raw`/`Box::from_raw` and do not provide additional allocator-mismatch protection or ASAN-risk reduction.

Therefore, when a value is wrapped in a *typed owner* that owns both halves of the raw round-trip (e.g., similar to patterns like `UvHandle<H,T>`, `WorkPool::schedule_owned`, etc.), it is the correct idiomatic form to use `Box::leak` and then `Box::from_raw` (open-coded) inside that wrapper. In this case, do not flag `Box::leak`/`Box::from_raw` usage as an allocator-mismatch risk and do not suggest replacing it with `heap::into_raw`/`take`.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31175
File: src/valkey/valkey_protocol.rs:407-408
Timestamp: 2026-05-22T01:23:05.156Z
Learning: In oven-sh/bun Rust crates configured with `edition = "2024"`, `size_of::<T>()` is available via the Rust 2024 prelude. When reviewing, do not flag `size_of::<T>()` calls as missing `use core::mem::size_of` or `use std::mem::size_of` imports in these crates.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31338
File: src/runtime/shell/shell_body.rs:923-926
Timestamp: 2026-05-24T10:04:48.082Z
Learning: When reviewing oven-sh/bun code, do NOT treat calls to `JSValue::get_own_truthy` as a “missing JS truthiness” check. Despite the misleading name, `get_own_truthy` only performs an own-property slot lookup (via `get_own`, no prototype walk) and filters out only `undefined` (internally: `!prop.is_undefined()`). Values like `""`, `false`, `0`, and `null` are allowed and should return `Some(...)`. So reviews should not flag uses for not excluding other falsy values; the correct semantic gate to expect is only the `undefined` guard.

Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 31335
File: src/install/PackageManager/PackageManagerDirectories.rs:842-848
Timestamp: 2026-05-24T10:58:13.503Z
Learning: When reviewing Rust code in this repo that indexes/slices a `PathBuffer` (which derefs to a `&[u8]` in `src/bun_core/util.rs`), treat slice indexing like `buf[..n]`, `buf[n..len]`, or `buf[len] = 0` as bounds-checked by Rust. Rust’s slice/indexing machinery will panic on out-of-bounds/overflow rather than write past the buffer. Therefore, do not flag these operations as potential out-of-bounds/unsafe writes, and do not recommend adding `debug_assert!` bounds checks that merely duplicate the existing Rust slice bounds checks (the checks are already enforced at indexing).

Learnt from: robobun
Repo: oven-sh/bun PR: 31579
File: src/sys/lib.rs:9225-9231
Timestamp: 2026-05-29T19:17:51.017Z
Learning: In this repo’s Rust code, don’t treat pattern matching on a borrowed enum/struct field as an incorrect “move-out-of-&” error when the pattern only binds payload fields that are `Copy` (e.g., matching `WriteFileData::Buffer { buffer }` where `buffer: &[u8]`, or `PathOrFileDescriptor::Path(bytes)` / `Fd(fd)` where `bytes: &[u8]` and `Fd` is `Copy`). Rust allows copying `Copy` payloads out of a borrowed aggregate without requiring the aggregate enum/struct itself to be `Copy`. In code review, if the compile error is about moving the aggregate but the bound fields are `Copy`, don’t recommend making the whole enum/struct `Copy` (e.g., don’t suggest `#[derive(Copy)]` on the enum solely for this match); keeping it non-`Copy` may be intentional for future non-`Copy` variants.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/jsc/VirtualMachine.rs:2598-2599
Timestamp: 2026-05-30T00:05:21.707Z
Learning: When reviewing Rust in this repo, do not flag direct `.0` access on `bun_core::String` as an encapsulation / sealed-field violation if it is used to reach the public inner newtype field and then call a public inner method (e.g., `bun_core::String::ascii(msg).0.mark_global()`). This usage is intended to preserve the prior Zig pattern (e.g., `...init(...).mark_global()`). Prefer considering `message.mark_global()`-style alternatives as style-only; only raise an issue if there is an actual API/compilation/semantic problem (e.g., method not public or code won’t compile / behavior changes), not merely because `.0` was accessed.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_fs.rs:6118-6119
Timestamp: 2026-05-30T00:05:31.762Z
Learning: When reviewing migration-only diffs that change implementation details like string types or API renames (e.g., ZigString -> BunString), do not treat pre-existing OOM handling patterns such as `.expect("oom")` as PR-introduced issues. Confirm in the base branch (before the change) that the same `.expect("oom")` usage pattern already existed; only flag it if the PR actually introduces or modifies the OOM-handling code path, not just the surrounding string/type/API migration.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/node/node_process.rs:398-398
Timestamp: 2026-05-30T00:05:32.312Z
Learning: In this codebase (oven-sh/bun), during ZigString → BunString migration reviews, treat `BunString::ascii(bytes)` **without** a subsequent `.with_encoding(...)` as byte-level equivalent to the old `ZigString::init(bytes)` **when the original Zig code had no encoding marking**. Therefore, do not flag `BunString::ascii(...)` (no `.with_encoding`) as a new encoding regression in that migration context. If the new code uses `.with_encoding(...)`, then encoding-related regression checks may still apply.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/server/server_body.rs:2002-2006
Timestamp: 2026-05-30T00:05:28.628Z
Learning: In this repo, avoid raising an issue just because code accesses the intentionally public inner tuple field of `bun_core::String` via `.0` (e.g., `some_bun_string.0.mark_utf8()`). Since `.0` is public and `bun_alloc::String::mark_utf8` is a public method, this pattern may be used to preserve base behavior during BunString/StringView migration. Only flag cases when there’s a separate, real problem (e.g., incorrect logic, misuse of the API), not merely the presence of `.0` field access.

Learnt from: dylan-conway
Repo: oven-sh/bun PR: 31590
File: src/runtime/webcore/Blob.rs:6941-6943
Timestamp: 2026-05-30T00:05:38.722Z
Learning: When reviewing this repo’s Rust code for the (BunString) “compile-breaking sealed-internals” concern, do not flag tuple-field access like `bun_string.0.mark_global()` / `bun_string.0.mark_utf8()` as sealed-internals—provided that the tuple field `.0` is publicly visible and the invoked `mark_*` methods are also public. In that case, treat it as a style preference at most. Only raise/retain the sealed-internals issue if, for the codebase version you’re reviewing, the tuple field `.0` is not public (or the target `mark_*` methods are not public), making the access compile-breaking.

- define_features!: drop the allow(non_upper_case_globals) from the pub use
  re-export arm where the lint cannot apply (useless_attribute x47)
- HardcodedModule: alias maps use bun_collections::HashMap per the
  disallowed-types policy (wyhash instead of SipHash)
- shell_parser, js_printer: byte paths/text Display via bstr::BStr instead
  of String::from_utf8_lossy
- shell_parser: drop_in_place(&raw mut ...) per borrow_as_ptr
- Loader: derive Default instead of the manual impl
- Yarn printer: JSON debug env check via bun_core::getenv_z
- pnpm: remove empty is_prod() placeholder branch
- WebSocketUpgradeClient: SslCtxOwned::into_raw via ManuallyDrop instead of
  mem::forget
- PostgresSQLConnection: remove redundant AnyPostgresError::from
- streams: return result instead of Ok(result?)
Comment thread src/ast/lib.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/bun_core/string/MutableString.rs (1)

516-521: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the UTF-16→UTF-8 size bound before calling the unsafe writer.

bytes.len() * 2 is not a safe upper bound here. A single UTF-16 code unit can expand to 3 UTF-8 bytes (for example U+0800..=U+FFFF outside surrogate pairs), so this tail can be undersized before copy_utf16_into_utf8 writes into it. The same assumption is also used in the buffered branch below, so non-ASCII BMP input can corrupt memory or drop data depending on how copy_utf16_into_utf8 handles a short output buffer.

🔧 Suggested direction
-            // SAFETY: copy_utf16_into_utf8 writes <= bytes.len*2; trimmed below.
+            // SAFETY: reserve for the worst-case UTF-8 expansion of arbitrary UTF-16.
             let tail =
-                unsafe { crate::vec::writable_slice(&mut self.context.list, bytes.len() * 2) };
+                unsafe { crate::vec::writable_slice(&mut self.context.list, bytes.len() * 3) };

You should make the same worst-case-capacity fix in the small-buffer path as well, or chunk/loop until the full UTF-16 input is encoded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bun_core/string/MutableString.rs` around lines 516 - 521, The allocation
for the UTF-16→UTF-8 write is using bytes.len() * 2 which is too small (UTF-16
units can expand up to 3 UTF-8 bytes); update the reservation before calling
unsafe crate::vec::writable_slice so the tail length uses the correct worst-case
bound (bytes.len() * 3) or otherwise ensure you loop/chunk until all UTF-16
input is encoded; apply the same fix to the buffered branch as well so functions
like copy_utf16_into_utf8, writable_slice, and the variables tail/decoded use a
safe output capacity to avoid buffer overruns or data loss.
src/bun_core/string/immutable.rs (1)

2477-2483: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the length bug in concat_if_needed's reuse check.

This fast path compares and advances by args.len() instead of arg.len(). With multiple chunks it can falsely conclude dest already matches the requested concatenation and skip rebuilding stale contents.

Suggested fix
-            if args.len() > remain.len() {
+            if arg.len() > remain.len() {
                 break 'brk true;
             }

-            if eql_long(&remain[0..args.len()], arg, true) {
-                remain = &remain[args.len()..];
+            if eql_long(&remain[0..arg.len()], arg, true) {
+                remain = &remain[arg.len()..];
             } else {
                 break 'brk true;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bun_core/string/immutable.rs` around lines 2477 - 2483, In
concat_if_needed, the reuse fast path incorrectly uses args.len() where it
should use the current chunk's length arg.len(), causing incorrect matches
across multiple chunks; update the three spots that reference args.len() (the
length comparison, the slice passed to eql_long, and the slice advance remain =
&remain[...]) to use arg.len() instead so the check and slicing operate on the
current chunk's length.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bun_core/string/immutable.rs`:
- Around line 2559-2561: Replace the hardcoded false passed into the
QuoteEscapeFormat construction with the actual
QuoteEscapeFormatFlags::ascii_only value so ASCII-only escaping is honored;
locate where QuoteEscapeFormat (or its constructor/formatter call) is created
(the place currently passing the literal false) and change that argument to
flags.ascii_only, and run/update tests that assert ASCII-only escaping behavior
to confirm non-ASCII characters are escaped when requested.

In `@src/bun_core/string/immutable/unicode.rs`:
- Around line 904-909: The public macro to_utf16_literal! should not be a plain
alias to the ASCII-only w! macro because that allows non-ASCII literals to be
mis-encoded; replace the alias with one of two fixes: either (A) implement a
proper UTF‑16 literal macro that converts a string literal into a u16 array of
UTF‑16 code units (so to_utf16_literal!("$s") expands to the correct u16
sequence), or (B) stop exporting the alias and instead make to_utf16_literal!
validate and reject non-ASCII input at compile time (use a macro that checks
bytes and emits compile_error! for non-ASCII), and keep the ASCII-only w!
separate; update or remove #[macro_export] accordingly and modify any callsites
that intended ASCII behavior to use w! explicitly.

---

Outside diff comments:
In `@src/bun_core/string/immutable.rs`:
- Around line 2477-2483: In concat_if_needed, the reuse fast path incorrectly
uses args.len() where it should use the current chunk's length arg.len(),
causing incorrect matches across multiple chunks; update the three spots that
reference args.len() (the length comparison, the slice passed to eql_long, and
the slice advance remain = &remain[...]) to use arg.len() instead so the check
and slicing operate on the current chunk's length.

In `@src/bun_core/string/MutableString.rs`:
- Around line 516-521: The allocation for the UTF-16→UTF-8 write is using
bytes.len() * 2 which is too small (UTF-16 units can expand up to 3 UTF-8
bytes); update the reservation before calling unsafe crate::vec::writable_slice
so the tail length uses the correct worst-case bound (bytes.len() * 3) or
otherwise ensure you loop/chunk until all UTF-16 input is encoded; apply the
same fix to the buffered branch as well so functions like copy_utf16_into_utf8,
writable_slice, and the variables tail/decoded use a safe output capacity to
avoid buffer overruns or data loss.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b37b878-77ce-45fe-b5d8-0b5a663355d9

📥 Commits

Reviewing files that changed from the base of the PR and between b43c712 and f936186.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (297)
  • scripts/build/features-json.ts
  • src/analytics/lib.rs
  • src/analytics/schema.rs
  • src/api/lib.rs
  • src/ast/ast_memory_allocator.rs
  • src/ast/ast_result.rs
  • src/ast/b.rs
  • src/ast/base.rs
  • src/ast/binding.rs
  • src/ast/char_freq.rs
  • src/ast/e.rs
  • src/ast/expr.rs
  • src/ast/fold_string_addition.rs
  • src/ast/g.rs
  • src/ast/import_record.rs
  • src/ast/known_global.rs
  • src/ast/lexer_log.rs
  • src/ast/lexer_tables.rs
  • src/ast/lib.rs
  • src/ast/loader.rs
  • src/ast/new_store.rs
  • src/ast/nodes.rs
  • src/ast/op.rs
  • src/ast/runtime.rs
  • src/ast/s.rs
  • src/ast/scope.rs
  • src/ast/server_component_boundary.rs
  • src/ast/stmt.rs
  • src/ast/symbol.rs
  • src/ast/target.rs
  • src/ast/transpiler_cache.rs
  • src/ast/ts.rs
  • src/ast/use_directive.rs
  • src/ast_jsc/lib.rs
  • src/base64/lib.rs
  • src/boringssl/lib.rs
  • src/boringssl_sys/boringssl.rs
  • src/brotli/lib.rs
  • src/brotli_sys/brotli_c.rs
  • src/bun.js.rs
  • src/bun_alloc/BufferFallbackAllocator.rs
  • src/bun_alloc/MaxHeapAllocator.rs
  • src/bun_alloc/MimallocArena.rs
  • src/bun_alloc/NullableAllocator.rs
  • src/bun_alloc/ast_alloc.rs
  • src/bun_alloc/baby_vec.rs
  • src/bun_alloc/basic.rs
  • src/bun_alloc/fallback.rs
  • src/bun_alloc/fallback/z.rs
  • src/bun_alloc/hashbrown_bridge.rs
  • src/bun_alloc/heap_breakdown.rs
  • src/bun_alloc/lib.rs
  • src/bun_alloc/maybe_owned.rs
  • src/bun_alloc/memory.rs
  • src/bun_alloc/stack_fallback.rs
  • src/bun_bin/lib.rs
  • src/bun_bin/phase_c_exports.rs
  • src/bun_core/Global.rs
  • src/bun_core/Progress.rs
  • src/bun_core/atomic_cell.rs
  • src/bun_core/bounded_array.rs
  • src/bun_core/debug.rs
  • src/bun_core/deprecated.rs
  • src/bun_core/env.rs
  • src/bun_core/env_var.rs
  • src/bun_core/external_shared.rs
  • src/bun_core/feature_flags.rs
  • src/bun_core/fmt.rs
  • src/bun_core/heap.rs
  • src/bun_core/hint.rs
  • src/bun_core/lib.rs
  • src/bun_core/output.rs
  • src/bun_core/result.rs
  • src/bun_core/string/HashedString.rs
  • src/bun_core/string/MutableString.rs
  • src/bun_core/string/SmolStr.rs
  • src/bun_core/string/StringBuilder.rs
  • src/bun_core/string/StringJoiner.rs
  • src/bun_core/string/escapeRegExp.rs
  • src/bun_core/string/identifier.rs
  • src/bun_core/string/immutable.rs
  • src/bun_core/string/immutable/exact_size_matcher.rs
  • src/bun_core/string/immutable/unicode.rs
  • src/bun_core/string/mod.rs
  • src/bun_core/string/write.rs
  • src/bun_core/string/wtf.rs
  • src/bun_core/thread_id.rs
  • src/bun_core/tty.rs
  • src/bun_core/util.rs
  • src/bun_core/windows_sys.rs
  • src/bun_core/wtf.rs
  • src/bun_core_macros/lib.rs
  • src/bun_output_tags/lib.rs
  • src/bundler/AstBuilder.rs
  • src/bundler/BundleThread.rs
  • src/bundler/Chunk.rs
  • src/bundler/DeferredBatchTask.rs
  • src/bundler/Graph.rs
  • src/bundler/HTMLImportManifest.rs
  • src/bundler/HTMLScanner.rs
  • src/bundler/IndexStringMap.rs
  • src/bundler/LinkerContext.rs
  • src/bundler/LinkerGraph.rs
  • src/bundler/OutputFile.rs
  • src/bundler/ParseTask.rs
  • src/bundler/PathToSourceIndexMap.rs
  • src/bundler/ServerComponentParseTask.rs
  • src/bundler/ThreadPool.rs
  • src/bundler/analyze_transpiled_module.rs
  • src/bundler/barrel_imports.rs
  • src/bundler/bundle_v2.rs
  • src/bundler/bundled_ast.rs
  • src/bundler/cache.rs
  • src/bundler/defines.rs
  • src/bundler/entry_points.rs
  • src/bundler/lib.rs
  • src/bundler/linker.rs
  • src/bundler/linker_context/MetafileBuilder.rs
  • src/bundler/linker_context/OutputFileListBuilder.rs
  • src/bundler/linker_context/StaticRouteVisitor.rs
  • src/bundler/linker_context/computeChunks.rs
  • src/bundler/linker_context/computeCrossChunkDependencies.rs
  • src/bundler/linker_context/convertStmtsForChunk.rs
  • src/bundler/linker_context/convertStmtsForChunkForDevServer.rs
  • src/bundler/linker_context/doStep5.rs
  • src/bundler/linker_context/findAllImportedPartsInJSOrder.rs
  • src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs
  • src/bundler/linker_context/findImportedFilesInCSSOrder.rs
  • src/bundler/linker_context/generateChunksInParallel.rs
  • src/bundler/linker_context/generateCodeForFileInChunkJS.rs
  • src/bundler/linker_context/generateCodeForLazyExport.rs
  • src/bundler/linker_context/generateCompileResultForCssChunk.rs
  • src/bundler/linker_context/generateCompileResultForHtmlChunk.rs
  • src/bundler/linker_context/generateCompileResultForJSChunk.rs
  • src/bundler/linker_context/postProcessCSSChunk.rs
  • src/bundler/linker_context/postProcessHTMLChunk.rs
  • src/bundler/linker_context/postProcessJSChunk.rs
  • src/bundler/linker_context/prepareCssAstsForChunk.rs
  • src/bundler/linker_context/renameSymbolsInChunk.rs
  • src/bundler/linker_context/scanImportsAndExports.rs
  • src/bundler/linker_context/writeOutputFilesToDisk.rs
  • src/bundler/options.rs
  • src/bundler/transpiler.rs
  • src/bundler_jsc/PluginRunner.rs
  • src/bundler_jsc/analyze_jsc.rs
  • src/bundler_jsc/lib.rs
  • src/bundler_jsc/options_jsc.rs
  • src/bundler_jsc/source_map_mode_jsc.rs
  • src/bunfig/arguments.rs
  • src/bunfig/bunfig.rs
  • src/bunfig/lib.rs
  • src/c-headers-for-zig.h
  • src/cares_sys/c_ares.rs
  • src/clap/args.rs
  • src/clap/comptime.rs
  • src/clap/lib.rs
  • src/clap/streaming.rs
  • src/clap_macros/lib.rs
  • src/codegen/bindgen-lib-internal.ts
  • src/codegen/bindgen-lib.ts
  • src/codegen/bindgen.ts
  • src/codegen/bindgenv2/internal/base.ts
  • src/codegen/bindgenv2/internal/dictionary.ts
  • src/codegen/bindgenv2/internal/union.ts
  • src/codegen/class-definitions.ts
  • src/codegen/cppbind.ts
  • src/codegen/generate-classes.ts
  • src/codegen/generate-host-exports.ts
  • src/codegen/generate-js2native.ts
  • src/codegen/generate-jssink.ts
  • src/collections/StaticHashMap.rs
  • src/collections/array_hash_map.rs
  • src/collections/array_list.rs
  • src/collections/bit_set.rs
  • src/collections/comptime_string_map.rs
  • src/collections/hive_array.rs
  • src/collections/identity_context.rs
  • src/collections/lib.rs
  • src/collections/linear_fifo.rs
  • src/collections/multi_array_list.rs
  • src/collections/pool.rs
  • src/collections/string_map.rs
  • src/collections/vec_ext.rs
  • src/collections/zig_hash_map.rs
  • src/crash_handler/CPUFeatures.rs
  • src/crash_handler/handle_oom.rs
  • src/crash_handler/lib.rs
  • src/csrf/lib.rs
  • src/css/compat.rs
  • src/css/context.rs
  • src/css/css_modules.rs
  • src/css/css_parser.rs
  • src/css/declaration.rs
  • src/css/dependencies.rs
  • src/css/error.rs
  • src/css/generics.rs
  • src/css/lib.rs
  • src/css/logical.rs
  • src/css/media_query.rs
  • src/css/prefixes.rs
  • src/css/printer.rs
  • src/css/properties/align.rs
  • src/css/properties/animation.rs
  • src/css/properties/background.rs
  • src/css/properties/border.rs
  • src/css/properties/border_image.rs
  • src/css/properties/border_radius.rs
  • src/css/properties/box_shadow.rs
  • src/css/properties/css_modules.rs
  • src/css/properties/custom.rs
  • src/css/properties/display.rs
  • src/css/properties/effects.rs
  • src/css/properties/flex.rs
  • src/css/properties/font.rs
  • src/css/properties/generate_properties.ts
  • src/css/properties/grid.rs
  • src/css/properties/list.rs
  • src/css/properties/margin_padding.rs
  • src/css/properties/masking.rs
  • src/css/properties/mod.rs
  • src/css/properties/outline.rs
  • src/css/properties/overflow.rs
  • src/css/properties/position.rs
  • src/css/properties/prefix_handler.rs
  • src/css/properties/properties_generated.rs
  • src/css/properties/properties_impl.rs
  • src/css/properties/shape.rs
  • src/css/properties/size.rs
  • src/css/properties/svg.rs
  • src/css/properties/text.rs
  • src/css/properties/transform.rs
  • src/css/properties/transition.rs
  • src/css/properties/ui.rs
  • src/css/rules/container.rs
  • src/css/rules/counter_style.rs
  • src/css/rules/custom_media.rs
  • src/css/rules/document.rs
  • src/css/rules/font_face.rs
  • src/css/rules/font_palette_values.rs
  • src/css/rules/import.rs
  • src/css/rules/keyframes.rs
  • src/css/rules/layer.rs
  • src/css/rules/media.rs
  • src/css/rules/mod.rs
  • src/css/rules/namespace.rs
  • src/css/rules/nesting.rs
  • src/css/rules/page.rs
  • src/css/rules/property.rs
  • src/css/rules/scope.rs
  • src/css/rules/starting_style.rs
  • src/css/rules/style.rs
  • src/css/rules/supports.rs
  • src/css/rules/tailwind.rs
  • src/css/rules/unknown.rs
  • src/css/rules/viewport.rs
  • src/css/selectors/builder.rs
  • src/css/selectors/mod.rs
  • src/css/selectors/parser.rs
  • src/css/selectors/selector.rs
  • src/css/small_list.rs
  • src/css/sourcemap.rs
  • src/css/targets.rs
  • src/css/values/alpha.rs
  • src/css/values/angle.rs
  • src/css/values/calc.rs
  • src/css/values/color.rs
  • src/css/values/color_generated.rs
  • src/css/values/css_string.rs
  • src/css/values/easing.rs
  • src/css/values/gradient.rs
  • src/css/values/ident.rs
  • src/css/values/image.rs
  • src/css/values/length.rs
  • src/css/values/mod.rs
  • src/css/values/number.rs
  • src/css/values/percentage.rs
  • src/css/values/position.rs
  • src/css/values/ratio.rs
  • src/css/values/rect.rs
  • src/css/values/resolution.rs
  • src/css/values/size.rs
  • src/css/values/syntax.rs
  • src/css/values/time.rs
  • src/css/values/url.rs
  • src/css_derive/lib.rs
  • src/css_jsc/color_js.rs
  • src/css_jsc/css_internals.rs
  • src/css_jsc/error_jsc.rs
  • src/dispatch/lib.rs
  • src/dns/lib.rs
  • src/dotenv/env_loader.rs
  • src/errno/darwin_errno.rs
  • src/errno/freebsd_errno.rs
  • src/errno/lib.rs
  • src/errno/linux_errno.rs
  • src/errno/windows_errno.rs
  • src/event_loop/AnyEventLoop.rs
💤 Files with no reviewable changes (6)
  • src/ast_jsc/lib.rs
  • src/bun_core/string/escapeRegExp.rs
  • src/bun_alloc/memory.rs
  • src/bun_core/feature_flags.rs
  • src/ast/s.rs
  • src/bun_alloc/basic.rs

Comment thread src/bun_core/string/immutable.rs
Comment thread src/bun_core/string/immutable/unicode.rs
Comment thread src/ast/lib.rs Outdated
Jarred-Sumner and others added 3 commits June 5, 2026 01:42
…guards

- range_of_identifier: include the final codepoint's width when an
  identifier runs to end-of-input, stop skipping the byte after a
  bracketed \u{...} escape (zero the cursor width after the manual
  advance), and handle an identifier that starts with a bracketed
  escape; add unit tests
- spawn_sync_inherit_impl: fail with FileNotFound on empty argv instead
  of panicking on the unix paths (matches the Windows arm)
- LinearFifo std::io::Write: follow the trait contract for
  fixed-capacity buffers (partial write / Ok(0) when full) instead of
  reporting ErrorKind::OutOfMemory
- w! macro: promote the ASCII-only debug_assert to a const assert so
  non-ASCII literals are a compile error in every profile
- errno: add a win32_errno_name translation test
- clap test helper: rename 'allocations' to 'expected_owned_results'
@Jarred-Sumner Jarred-Sumner merged commit 8553428 into main Jun 5, 2026
72 of 78 checks passed
@Jarred-Sumner Jarred-Sumner deleted the claude/todo-audit-fixes branch June 5, 2026 03:31
Jarred-Sumner pushed a commit that referenced this pull request Jun 5, 2026
…eErrors (#31874)

### What

Fixes a GC use-after-free found by fuzzing (ASAN fingerprint
`heap-use-after-free`, read of size 1 at offset 144 inside a 152-byte
region: that is exactly `BuildMessage.logged` inside a freed
`Box<BuildMessage>`, confirmed via DWARF layout).

### Root cause

JSC roots JSValues through the conservative stack scan. Values that live
only in a malloc'd `Vec<JSValue>` buffer are invisible to the GC.

`process_fetch_log` (module fetch failure path) created one
`BuildMessage`/`ResolveMessage` wrapper per log message and pushed each
into a heap `Vec<JSValue>` before handing the slice to
`create_aggregate_error`. Every `create` call in that loop allocates JS
cells, so a collection can run mid-loop; the wrappers created in earlier
iterations have no reachable reference at that point, get swept, and
their finalizers free the native `BuildMessage`. Anything that touches
the stale cells afterwards (printing the unhandled rejection, reading
`.message` off `error.errors[i]`) reads freed memory. The original Zig
implementation used an on-stack `var errors_stack: [256]JSValue`
specifically so the conservative scan would root these; the port
replaced it with a heap `Vec`.

The fixing lines are the `Vec<JSValue>` to `[JSValue; 256]` changes in
`src/jsc/VirtualMachine.rs` (`process_fetch_log`) and `src/jsc/lib.rs`
(`LogJsc::to_js`, same pattern, also feeds `create_aggregate_error`).
`ast_jsc::log_to_js` already used the on-stack array and documents why.

Same bug shape in `bake/production.rs`: `css_chunk_js_strings` held
freshly created `JSString`s in a heap `Vec` across
`preload_bundled_module` calls (which evaluate JS modules). That one is
unbounded in length, so each value is kept `protect()`ed (RAII
`ProtectedJSValue`) until consumed. An audit of the other `Vec<JSValue>`
sites in the tree found them safe (values rooted elsewhere via
`MarkedArgumentBuffer`, `protect()`, `Strong`, or no allocation while
held).

### Reproduction

A module with 257 duplicate `const` declarations produces 256 log
messages. Importing it 16 times with
`BUN_JSC_slowPathAllocsBetweenGCs=100` crashed the unfixed ASAN debug
build 6/6 runs (ASAN UAF or `ASSERTION FAILED: isSymbol()` from
swept-and-reused cells, depending on timing). The fuzzer hit the same
window on a Worker thread with its `Bun.gc(true)` suffix.

### Tests

`test/js/bun/resolve/build-error.test.ts`: "import with many build
errors keeps AggregateError entries alive across GC". Fails on the
unfixed ASAN debug build (child aborts, 6/6), passes on the fixed build
(15/15 runs). It passes under a release build either way since the UAF
needs ASAN (or unlucky reuse) to become observable; the ASAN CI suites
are the enforcing ones.
`test/js/bun/transpiler/transpiler-error-gc-uaf.test.ts` covers the
`LogJsc::to_js` site directly: `Bun.Transpiler().transformSync` with 256
parse errors under `BUN_JSC_useZombieMode` and
`BUN_JSC_collectContinuously` crashes the unfixed ASAN build (zapped
StructureID while marking the errors array) and passes on this branch.
Existing `plugins.test.ts`, `transpiler.test.js`, `dev-and-prod.test.ts`
(5 PROD variants) and `framework-router.test.ts` pass.


### Linked issue

Fixes #23181

That report is the same scenario: a loop of dynamic imports over files
where some fail with build errors, with a try/catch around each. A
script mirroring its `extract-messages.ts` shape (sequential `await
import()` of modules with multiple build errors, catch block touching
the error) crashes the unfixed ASAN build 3/3 runs under GC pressure and
completes cleanly 3/3 on this branch. The original report was against
v1.2.23, whose implementation of this path differed, so the exact 1.2.23
mechanism can't be re-verified, but the reported workload no longer
crashes on current Bun with this change.



### Supersedes #30671

That earlier PR fixed the same two sites (`process_fetch_log`,
`LogJsc::to_js`) but is now conflicting with main after the comment
rewrites in #31783. This PR carries the same changes rebased on current
main, absorbs its transpiler regression test, and additionally fixes the
same unrooted-heap-Vec shape in bake's production build.
robobun added a commit that referenced this pull request Jun 6, 2026
The rebase over #31783 reverted the CoreFoundation/CoreServices no-deinit
comments back to commented-out Zig blocks; restore main's wording, which
also carries the leaked-for-process-lifetime invariant the Send SAFETY
comment references.
robobun added a commit that referenced this pull request Jun 6, 2026
Restore main's wording for the two reverted hunks (module doc, PathWatcher::new
doc) and replace the remaining Zig-era identifiers in comments with the real
Rust names (deinit -> FSEventsWatcher::drop / remove_watch, addWatch/removeWatch,
onFSEvent, unregisterWatcher, walkAndAdd, addOne, getOrPut, getFdPath,
onPathUpdatePosix, runExitCallbacks, Zig enum-literal syntax).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants