Repository navigation
Conversation
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
|
Updated 9:41 PM PT - Jul 6th, 2026
❌ @robobun, your commit 74e3329 has 1 failures in 🧪 To try this PR locally: bunx bun-pr 33549That installs a local version of the PR into your bun-33549 --bun |
There was a problem hiding this comment.
LGTM — strict improvement over the previous clobbering behavior, refcounting is balanced on all paths, and the follow-up noted inline is a pre-existing gap.
Extended reasoning...
Overview
This PR fixes the export shell builtin so that export NAME (no =) exports the variable's current value instead of overwriting it with an empty string. It touches ~30 lines in src/runtime/shell/builtin/export.rs (splitting the None match arm out to look up the existing value in shell_env/export_env before inserting) and adds 3 regression tests to test/js/bun/shell/bunshell.test.ts covering the three repro cases (shell var, inherited env var, unset name).
Security risks
None. This is shell-builtin bookkeeping over the interpreter's own env maps; no new untrusted-input parsing, no path handling, no network/auth surface. The unsafe blocks are the same raw-pointer-to-ShellState pattern already used in the unchanged Some(eq) arm.
Level of scrutiny
Moderate — it touches manual refcounting via EnvStr/EnvMap, which is memory-safety-sensitive. I traced the refcounts against EnvMap::get (bumps the returned value, caller must deref) and EnvMap::insert (refs the key only if new, derefs the old value, refs the new value):
- found in shell_env:
get+1 on val →insertrefs val again (+1, stored) and refs/keeps label →val.deref()balances theget→label.deref()balances thedupe_ref_counted. Net: map holds one ref each. ✓ - found in export_env (re-inserting same value at same key):
get+1 →insertderefs the stored copy (-1, still alive because of thegetbump) then refs val (+1, stored) →val.deref()andlabel.deref()balance. ✓ - unset: only
labelis duped and immediately deref'd; nothing inserted. ✓
No leaks or use-after-free on any branch.
Other factors
- The inline nit (later
T=6afterexport Tdoesn't propagate toexport_env) is a pre-existing limitation ofassign_var'sAssignCtx::Shellarm and also affectsexport T=5; T=6today — not introduced or worsened here. The PR is a strict improvement. - Moving
let shell = ...above thematchis safe:arg_bytesreturns an unbounded-lifetime slice into separately-allocated argv storage per its doc comment (Builtin.rs:478-487). - Tests spawn a child
bun -eto observeprocess.env, which correctly exercises the child-env-construction path (export_env → child), and useTestBuilderconsistent with neighboringexporttests.
There was a problem hiding this comment.
LGTM — small, well-scoped fix; refcounts balance against the documented EnvMap get/insert contract, and the three tests cover the behavioral matrix.
Extended reasoning...
Overview
This PR changes one function in src/runtime/shell/builtin/export.rs (the export builtin's argument loop) and adds three regression tests to test/js/bun/shell/bunshell.test.ts. The Some(eq) arm is byte-identical to the pre-existing code, just moved into a match. The only new logic is the ~12-line None arm, which looks up the current value via shell_env.get() / export_env.get() and re-inserts it into export_env instead of writing an empty string.
Security risks
None. This is a shell builtin's variable-bookkeeping path; no untrusted input parsing, no filesystem/network I/O, no auth or crypto surface.
Level of scrutiny
Moderate — it's unsafe Rust with manual EnvStr refcounting, which is the most-blocked review category per repo guidance. I traced the refcounts on every terminal path against the actual EnvMap implementation:
EnvMap::getreturns a +1 ref (line 107:val.ref_()) and its doc says "deref the string when done" — the new code doesval.deref()afterinsert. ✓EnvMap::inserttakes its own +1 on both key and value (lines 79/83) — the caller's local +1 onlabelis released vialabel.deref()on all three sub-paths (found in shell_env / found in export_env / unset). ✓- The re-insert-same-value case (export of an already-exported name) can't UAF because
get()'s +1 keeps the value alive acrossinsert's internal deref-old-value step. ✓ - The unset case skips
insertentirely and just releaseslabel. ✓
Other factors
The three added tests exercise each branch (shell var → exported, inherited var preserved, unset name stays unset) and follow the existing TestBuilder pattern in the same describe block. My earlier inline note about the pre-existing AssignCtx::Shell routing gap was acknowledged as out-of-scope and resolved; it's a strict improvement over current behavior with no regression. No CODEOWNERS on this path. The bug-hunting pass found nothing.
|
CI status: the diff is green. The two failing |
|
Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-06, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main. |
What
In Bun Shell, the
exportbuiltin treated every operand asNAME=<value-after-equals>. With no=present the value parsed as the empty string and was then assigned into the exported env table. This broke the canonical POSIX two-step export idiom and, worse, destroyed existing values.Repro
Cause
src/runtime/shell/builtin/export.rssplit each operand on=; theNonearm fell through to(name, "")and inserted that empty value intoexport_env, overwriting any existing binding and materializing entries for unset names.Fix
export NAMEwith no=now only sets the export attribute: it looks up the current value (shell variable first, then an already-exported/inherited one) and exports that, and does nothing when the name is unset.export NAME=valueis unchanged.Verification
Added three regression tests to
test/js/bun/shell/bunshell.test.ts(theexportidiom, export of an inherited var, export of an unset name). All fail onmainand pass with this change.