Skip to content

shell: keep trailing empty variant when parsing nested brace groups - #34865

Merged
Jarred-Sumner merged 3 commits into
mainfrom
farm/aa82fa09/shell-brace-nested-trailing-empty
Jul 21, 2026
Merged

Jarred-Sumner merged 3 commits into
mainfrom
farm/aa82fa09/shell-brace-nested-trailing-empty

Conversation

@robobun

@robobun robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

When a nested brace group ends with an empty alternative ({a,}), the nested-expansion parser drops that variant. calculate_expanded_amount still counts it, so the output array is sized for N variants but only N-1 are written, leaving the last slot as an empty string.

Bun.$.braces("{x,a{,}b}")    // ["x","ab",""]       bash: x ab ab
Bun.$.braces("{x,{a,}}z")   // ["xz","az",""]      bash: xz az z
Bun.$.braces("a{b,c{d,}}e") // ["abe","acde",""]   bash: abe acde ace

Only the nested (contains_nested) path is affected; the flat path handles trailing empties correctly via build_expansion_table. Leading ({,a}) and middle ({a,,b}) empties already work.

Cause

In Parser::parse_expansion, after the inner loop breaks on Comma and pushes a variant, the outer while !self.match_any(&[Close, Eof]) peeks Close, consumes it, and exits without pushing the pending empty variant.

Fix

Make the inner loop the sole consumer of Close/Eof. Every iteration of the outer loop now pushes exactly one variant and terminates only after the variant following a trailing comma has been recorded. This also brings the parser into agreement with calculate_expanded_amount, which already counted the trailing empty.

Collateral: nested {}

The same guard also caused a nested comma-free {} to parse to zero variants. With zero variants expand_nested returns early without bubbling up, so any text after the {} in that branch was dropped:

Bun.$.braces("{x,a{}b}")  // was ["x","a"]   now ["x","ab"]
Bun.$.braces("{a,b{}}c") // was ["ac","b"]  now ["ac","bc"]

After this change a nested {} parses to one empty variant, which matches what calculate_expanded_amount already computed and what expand_flat already does for a{}b. Bash treats a comma-free {} as literal text, so neither behavior is bash-exact; that broader question is the subject of #34856. This change only brings the nested parser into agreement with the slot count and the flat path.

Verification

Added a test.each block in test/js/bun/shell/brace.test.ts covering trailing, leading, and middle empty variants at various nesting positions, sibling products of trailing-empty groups, and the nested {} suffix case. 10 of the 14 new cases fail on the released binary and all pass with this change; existing $.braces, bunshell, parse, and lex suites remain green.


[review] gate passed · iteration 0 · 2 files touched

fails on main (without fix)
ASAN without fix: 10 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/shell/brace.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (0261e126a)

test/js/bun/shell/brace.test.ts:
(pass) $.braces > no-op [2.54ms]
(pass) $.braces > 2 [1.83ms]
(pass) $.braces > 3 [1.57ms]
(pass) $.braces > nested [1.87ms]
(pass) $.braces > nested 2 [1.94ms]
(pass) $.braces > nested sibling product [1.53ms]
(pass) $.braces > nested sibling product with surrounding text [1.80ms]
(pass) $.braces > nested sibling product mixed with variants [1.70ms]
(pass) $.braces > nested sibling product triple [2.14ms]
65 |       // empty variant, matching calculate_expanded_amount and expand_flat.
66 |       ["{x,a{}b}", ["x", "ab"]],
67 |       ["{a,b{}}c", ["ac", "bc"]],
68 |       ["{x,{}y}", ["x", "y"]],
69 |     ])("%s", (pattern, expected) => {
70 |       expect($.braces(pattern)).toEqual(expecte
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (0261e126a)

test/js/bun/shell/brace.test.ts:
(pass) $.braces > no-op [0.05ms]
(pass) $.braces > 2 [0.06ms]
(pass) $.braces > 3 [0.02ms]
(pass) $.braces > nested [0.05ms]
(pass) $.braces > nested 2 [0.03ms]
(pass) $.braces > nested sibling product [0.02ms]
(pass) $.braces > nested sibling product with surrounding text [0.02ms]
(pass) $.braces > nested sibling product mixed with variants [0.03ms]
(pass) $.braces > nested sibling product triple [0.03ms]
(pass) $.braces > nested with empty variant > {x,a{,}b} [0.02ms]
(pass) $.braces > nested with empty variant > {x,{a,}}z [0.01ms]
(pass) $.braces > nested with empty variant > {x,{,a}}z
(pass) $.braces > nested with empty variant > {x,{,}}z
(pass) $.braces > nested with empty variant > a{b,c{d,}}e
(pass) $.braces > nested with empty variant > a{b,c{,d}}e
(pass) $.braces > nested with empty variant > {x,{a,,b}}
(pass) $.braces > nested with empty variant > {x,{a,b,}}
(pass) $.braces > nested with empty variant > {{a,},x}
(pass) $.braces > nested with empty variant > {{a,}{b,}}
(pass) $.braces > nested with empty variant > p{q,{r,}{s,}}t [0.01ms]
(pass) $.braces > nested with empty variant > {x,a
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/shell/brace.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (0261e126a)

test/js/bun/shell/brace.test.ts:
(pass) $.braces > no-op [2.32ms]
(pass) $.braces > 2 [1.84ms]
(pass) $.braces > 3 [1.58ms]
(pass) $.braces > nested [1.85ms]
(pass) $.braces > nested 2 [1.94ms]
(pass) $.braces > nested sibling product [1.52ms]
(pass) $.braces > nested sibling product with surrounding text [1.83ms]
(pass) $.braces > nested sibling product mixed with variants [1.70ms]
(pass) $.braces > nested sibling product triple [2.22ms]
(pass) $.braces > nested with empty variant > {x,a{,}b} [1.69ms]
(pass) $.braces > nested with empty variant > {x,{a,}}z [0.64ms]
(pass) $.braces > nested with empty variant > {x,{,a}}z [0.50ms]
(pass) $.braces > nested with empty variant > {x,{,}}z [0.49ms]
(pass) $.braces > nested with 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 717ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] gen NodeModuleModule.lut.h
Generating /workspace/bun/build/release/codegen/NodeModuleModule.lut.h from /workspace/bun/src/jsc/modules/NodeModuleModule.cpp
[2/7] gen cpp.rs (cppbind)
[2/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

info: checking for self-update (current version: 1.29.0)
  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bu
... (truncated)
diff hotspot
src/shell_parser/braces.rs      | 25 ++++++++++++-------------
 test/js/bun/shell/brace.test.ts | 27 +++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 13 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                             reads  edits  tests
src/shell_parser/braces.rs           4      1      0
test/js/bun/shell/brace.test.ts      1      2      0

root cause · written by the author bot

The outer loop guard in parse_expansion consumed the Close token immediately after a trailing comma in a nested brace group, so the pending empty variant was never pushed even though calculate_expanded_amount had already counted it, leaving the last output slot unwritten. The fix restructures the loop so that only the inner loop consumes Close or Eof, ensuring every outer iteration pushes exactly one variant before the close check runs. As a side effect, a nested comma-free {} now yields one empty variant instead of zero, which also corrects expand_nested dropping text that fo…

Parser::parse_expansion's outer loop guard consumed the closing brace
after a trailing comma, so the pending empty variant was never pushed.
calculate_expanded_amount still counted it, leaving the last output
slot blank: {x,a{,}b} expanded to ["x","ab",""] instead of
["x","ab","ab"]. The non-nested expand_flat path was already
correct.

Make the inner loop the sole consumer of Close/Eof so every variant,
including the trailing empty, is pushed before the outer loop exits.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3acda92d-b18a-41b1-b484-c0f5f45f1d82

📥 Commits

Reviewing files that changed from the base of the PR and between a4c1ddd and 0261e12.

📒 Files selected for processing (2)
  • src/shell_parser/braces.rs
  • test/js/bun/shell/brace.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Reproduced with USE_SYSTEM_BUN=1 bun test test/js/bun/shell/brace.test.ts -t 'nested with empty variant' (10 of the 14 new cases fail); passes with bun bd test test/js/bun/shell/brace.test.ts.

Self-review surfaced one concern: the loop restructure also changes nested comma-free {} from 0 to 1 variant (fixing a suffix-drop), which is now tested and documented in the PR body.

@robobun

robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 8:26 PM PT - Jul 20th, 2026

✅ @robobun, your commit 0261e126ac14838c748d5335e8b38936c35ab915 passed in Build #76535! 🎉


🧪   To try this PR locally:

bunx bun-pr 34865

That installs a local version of the PR into your bun-34865 executable, so you can run:

bun-34865 --bun

@claude claude 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.

LGTM — small, well-scoped control-flow fix in parse_expansion with thorough coverage.

What was reviewed:

  • Traced the new loop against {a,}, {{d,e}{g,h}}, {123,{456,789},abc}, and the deeply-nested test — all produce the same variant lists as before except the trailing-empty case, which now agrees with calculate_expanded_amount.
  • Checked the None => break true arm: match_any([Close, Eof]) runs first, so parse_atom cannot see Eof here — the arm is defensive only, no behavior change vs. the old None => break.
  • Checked {} inside a nested group: now yields one empty variant instead of zero; expand_nested handles Many([]) via the start >= many_len bubble-up, and the slot count from calculate_expanded_amount is unchanged.
  • Verified each new test vector against bash's expansion; robobun confirmed 8/11 fail on the released binary.
Extended reasoning...

Overview

The PR fixes a bug in src/shell_parser/braces.rs::Parser::parse_expansion where a nested brace group ending in a trailing comma ({a,}) dropped the final empty variant. The old outer while !self.match_any(&[Close, Eof]) guard consumed the Close token immediately after a Comma broke the inner loop, so the pending empty variant was never pushed. Because calculate_expanded_amount already counted that variant, the output array was oversized and the last slot surfaced as an empty string. The fix moves Close/Eof consumption exclusively into the inner loop and makes the outer loop unconditional, so every iteration pushes exactly one variant before checking close. A test.each block with 11 cases (trailing/leading/middle empties, sibling products, prefix/suffix text) is added to test/js/bun/shell/brace.test.ts.

Security risks

None. This is a pure control-flow restructure inside a bump-arena parser for shell brace-expansion syntax. No new allocations, no pointer arithmetic changes, no untrusted-length handling. The existing MAX_BRACE_GROUPS guard and check_brace_group_count still apply unchanged.

Level of scrutiny

Medium — parser logic, but the change is a ~15-line loop restructure whose invariant ("each outer iteration pushes one variant, terminates on Close/Eof") is easy to verify by hand. I traced the new loop against every existing test in the file (nested, nested 2, nested sibling product ×4, very deeply nested) and confirmed the variant sequences are identical. The only semantic delta beyond the fix is that a bare {} inside a nested group now parses to one empty variant instead of zero; expand_nested already handles GroupAtoms::Many with length 0 via the start >= many_len bubble-up path, and calculate_expanded_amount already counted it as 1, so this is a strict improvement in parser/counter agreement rather than a regression.

Other factors

  • The None => break true arm (formerly None => break with close left false) is unreachable in practice: parse_atom returns None only on Eof, and the inner loop's match_any(&[Close, Eof]) check runs first. Treating it as close = true is the more defensive choice and avoids a spurious extra outer iteration if it ever were reached.
  • Test vectors were spot-checked against bash's actual expansion; all 11 match.
  • robobun confirmed USE_SYSTEM_BUN=1 fails 8 of the 11 new cases (the 3 that pass are the leading/middle-empty controls the PR says already worked) and the debug build passes all — the tests are load-bearing.
  • Tests are added to the existing brace.test.ts file per repo convention, use test.each for the matrix, and assert exact arrays with toEqual.

@claude claude 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.

LGTM — small, well-scoped control-flow fix that brings parse_expansion into agreement with calculate_expanded_amount.

What was reviewed

  • Traced the new loop { let close = loop { ... } } structure against {a,}, {}, {{a,},x}, and {{a,}{b,}} — each now pushes exactly one variant per outer iteration, including the trailing empty.
  • Checked that {} (which previously produced 0 variants, now 1 empty variant) is only reachable on the nested path and yields identical output through expand_nested — no observable behavior change there.
  • Confirmed the parse_atom()? => None arm is effectively unreachable (Eof is consumed by match_any first) and break true matches the old net effect.
  • Test coverage hits trailing/leading/middle empties, sibling products, and prefix/suffix text; robobun confirmed 8/11 fail on system Bun and all pass on the debug build.
Extended reasoning...

Overview

Two files: a ~15-line control-flow reshape in src/shell_parser/braces.rs::Parser::parse_expansion, and an 11-case test.each block added to test/js/bun/shell/brace.test.ts. The bug was that the outer while !self.match_any(&[Close, Eof]) guard consumed Close before the trailing empty variant after a comma could be pushed, so nested {a,} produced one variant while calculate_expanded_amount sized the output array for two. The fix removes the outer guard and makes the inner loop the sole consumer of Close/Eof, returning a close: bool from a labeled-break loop so every outer iteration unconditionally pushes exactly one variant.

Security risks

None. This is pure in-memory string expansion over already-tokenized input. The existing MAX_BRACE_GROUPS / MAX_NESTED_BRACES DoS bounds are untouched, and the change cannot cause the parser to consume fewer tokens than before (every path still advances via match_any/r#match/parse_atom), so no new infinite-loop surface.

Level of scrutiny

Medium — it's a parser correctness fix with recursive downstream consumers (expand_nested), so I hand-traced several inputs including the new edge case where an immediately-closing group ({} in nested position) now yields one empty Many([]) variant instead of zero variants. That variant is a zero-length Many, which expand_nested handles via the start >= many_len bubble-up path, producing identical output to before. The None => break true arm (previously None => break with close=false) is unreachable in practice because match_any([Close, Eof]) runs first, and even if reached the net behavior is identical (old code would push the variant then exit on the next outer-loop guard).

Other factors

Tests are placed in the existing brace.test.ts alongside related coverage, use test.each per house style, and cover the full variant matrix (trailing/leading/middle empties × leading/trailing/interior nesting × with/without surrounding text × sibling products). robobun independently confirmed the tests fail on the released binary and pass with the fix. No prior human review comments to address. The comment above the inner loop is concise and states the invariant. This is exactly the kind of localized bugfix that doesn't need a human gate.

The loop restructure also makes a nested comma-free {} parse to one
empty variant instead of zero, so expand_nested no longer drops the
text after it. Pin that with tests.

@claude claude 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.

LGTM — focused parser fix that makes the nested-brace path agree with calculate_expanded_amount and bash.

What was reviewed

  • Traced parse_expansion old vs new for {a,}, {}, {a,,b} — inner loop is now the sole consumer of Close/Eof, so every outer iteration pushes exactly one variant; no infinite-loop risk (Eof/Close/Comma/parse_atom→None all terminate).
  • Confirmed the {}→1-empty-variant change is safe in expand_nested (empty Many hits the start >= many_len bubble-up path, so trailing text is no longer dropped).
  • Verified calculate_expanded_amount already counted the trailing empty and nested {} as 1, so out-array sizing now matches what the expander writes.
  • Test matrix covers trailing/leading/middle empties, sibling products of trailing-empty groups, and nested comma-free {}; robobun confirmed 8/11 fail on system bun and all pass on the debug build.
Extended reasoning...

Overview

Rewrites the outer/inner loop structure of Parser::parse_expansion in src/shell_parser/braces.rs so that only the inner loop consumes Close/Eof. Previously the outer while !self.match_any(&[Close, Eof]) guard would eat the Close immediately after a trailing comma, dropping the pending empty variant. The new shape guarantees each outer iteration pushes exactly one variant before checking close. A secondary effect: a nested comma-free {} now yields one empty variant instead of zero, which fixes expand_nested returning early and dropping text after it. 14 new test.each cases in test/js/bun/shell/brace.test.ts pin the behavior.

Security risks

None. Pure parser control-flow reshaping over already-lexed tokens; no new allocation sites, no unsafe blocks touched, no user-controlled sizes introduced. The existing MAX_BRACE_GROUPS bound and check_brace_group_count still gate recursion depth.

Level of scrutiny

Medium — this is a recursive-descent parser feeding expand_nested (which uses raw-pointer backrefs), so I traced termination and downstream handling of the new empty-Many variant shape. Termination: every inner-loop arm either consumes a token (match_any/r#match/parse_atom→advance) or breaks; Eof is idempotent under advance and yields break true. Downstream: an empty Many([]) group in expand_nested takes the start >= many_len branch and bubbles up correctly, so no new 0-length-slice hazards. calculate_expanded_amount was already counting these variants (Close contributes acc + segment_product with segment_product initialized to 1), so the out-array size and the number of writes now agree — the original symptom (last slot left empty) is eliminated at the root.

Other factors

  • The flat (non-nested) path via build_expansion_table already handled trailing empties and is untouched.
  • parse_expansion and the ast types have no consumers outside braces.rs.
  • robobun confirmed the fails-before/passes-after contract (USE_SYSTEM_BUN=1 fails 8 of the new cases).
  • Tests use test.each with exact toEqual assertions against bash-matching outputs, placed alongside existing $.braces coverage.

@Jarred-Sumner
Jarred-Sumner merged commit 76a7700 into main Jul 21, 2026
81 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/aa82fa09/shell-brace-nested-trailing-empty branch July 21, 2026 03:49
@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

Four of the nested with empty variant cases added here conflict with #34856's lexer change (comma-less {} is literal) and fail on main; follow-up in #34882.

Jarred-Sumner pushed a commit that referenced this pull request Jul 21, 2026
…-5.2 literal `{}` (#34882)

Fixes `test/js/bun/shell/brace.test.ts` which is red on main (four hard
failures on every lane, e.g. [build
76627](https://buildkite.com/bun/bun/builds/76627)).

## Cause

#34856 and #34865 were developed in parallel and merged three minutes
apart. #34856 changed the brace lexer so a `{...}` group with no
top-level comma is demoted to literal text (bash 5.2 semantics) before
the parser sees it. #34865 added a `nested with empty variant`
`test.each` block whose last four entries still expect the pre-#34856
behaviour. #34865's own commit message notes #34856 was addressing the
literal-`{}` question separately; the rebase was textually clean so
neither PR's CI caught the semantic overlap.

```
$.braces > nested with empty variant > {{a,}{b,}}   expected ["ab","a","b",""]   got ["{ab}","{a}","{b}","{}"]
$.braces > nested with empty variant > {x,a{}b}     expected ["x","ab"]          got ["x","a{}b"]
$.braces > nested with empty variant > {a,b{}}c     expected ["ac","bc"]         got ["ac","b{}c"]
$.braces > nested with empty variant > {x,{}y}      expected ["x","y"]           got ["x","{}y"]
```

bash 5.2.37 agrees with the new output in every case:

```
$ bash -c 'printf "[%s] " {{a,}{b,}}'   # [{ab}] [{a}] [{b}] [{}]
$ bash -c 'printf "[%s] " {x,a{}b}'     # [x] [a{}b]
$ bash -c 'printf "[%s] " {a,b{}}c'     # [ac] [b{}c]
$ bash -c 'printf "[%s] " {x,{}y}'      # [x] [{}y]
```

## Fix

All four entries are removed from the `nested with empty variant` block:
after #34856 none of them reach the `parse_expansion`/`expand_nested`
path that block guards, because the lexer demotes their comma-less group
to text first. The remaining ten entries in that block (notably
`p{q,{r,}{s,}}t`, which still routes through `expand_nested` with
sibling trailing-empty groups) continue to cover #34865's fix.

- `{{a,}{b,}}` is moved to the `comma-less brace group is literal (bash
5.2)` block with the bash-correct expectation
`["{ab}","{a}","{b}","{}"]`. That block also runs each case through the
actual shell in a subprocess, so the pattern now gets both `$.braces()`
and end-to-end shell coverage.
- `{x,a{}b}`, `{a,b{}}c`, `{x,{}y}` are dropped together with their
now-inaccurate comment (which describes a 0-variant `{}` reaching the
parser, a state that no longer exists). Their literal-`{}` behaviour is
already asserted by `p{q{},r}s` / `{a,b{}}z` / `{a,{}}z` in that same
block.

No `src/` change: the runtime behaviour is correct (matches bash); only
the stale expectations are realigned.

## Verification

```
bun bd test test/js/bun/shell/brace.test.ts
# before: 39 pass, 4 fail
# after:  40 pass, 0 fail
```

<!-- robobun:evidence:begin -->

---

**[stamp-90s]** gate passed · iteration 0 · 1 files touched

<details><summary>passes on PR (with fix)</summary>

```console
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/bun/shell/brace.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/bun/shell/brace.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (0ecd8eb)

test/js/bun/shell/brace.test.ts:
(pass) $.braces > no-op [1.89ms]
(pass) $.braces > 2 [1.81ms]
(pass) $.braces > 3 [1.77ms]
(pass) $.braces > nested [1.63ms]
(pass) $.braces > nested 2 [2.01ms]
(pass) $.braces > nested sibling product [1.71ms]
(pass) $.braces > nested sibling product with surrounding text [1.44ms]
(pass) $.braces > nested sibling product mixed with variants [1.97ms]
(pass) $.braces > nested sibling product triple [2.04ms]
(pass) $.braces > nested with empty variant > {x,a{,}b} [1.46ms]
(pass) $.braces > nested with empty variant > {x,{a,}}z [0.65ms]
(pass) $.braces > nested with empty variant > {x,{,a}}z [0.48ms]
(pass) $.braces > nested with empty variant > {x,{,}}z [0.47ms]
(pass) $.braces > nested with empty variant > a{b,c{d,}}e [0.52ms]
(pass) $.braces > nested with empty variant > a{b,c{,d}}e [0.48ms]
(pass) $.braces > nested with empty variant > {x,{a,,b}} [0.74ms]
(pass) $.braces > nested with empty variant > {x,{a,b,}} [0.47ms]
(pass) $.braces > nested with empty variant > {{a,},x} [0.49ms]
(pass) $.braces > nested with empty variant > p{q,{r,}{s,}}t [0.51ms]
(pass) $.braces > very deeply nested [2.81ms]
(pass) $.braces > empty string [2.88ms]
(pass) $.braces > unicode [2.43ms]
(pass) brace + glob composition > src/*.{ts,tsx} globs after brace expansion [62.79ms]
(pass) brace + glob composition > {src,lib}/*.ts composes a brace prefix with a glob [27.96ms]
(pass) brace + glob composition > an interpolated comma inside a brace group is one literal branch [22.66ms]
(pass) $.braces input bounds > rejects a word with an excessive number of br
... (truncated)
Exit: 0
```

</details>

<details><summary>diff hotspot</summary>

```
test/js/bun/shell/brace.test.ts | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                             reads  edits  tests
test/js/bun/shell/brace.test.ts      2      3      0
```

</details>

<!-- robobun:evidence:end -->
robobun added a commit that referenced this pull request Jul 21, 2026
Picks up #34882 (brace.test.ts nested-empty-variant fix): faa769d
merged main at 13f5dec, before #34882 landed, so 76654 had the
briefly-broken brace.test.ts from #34865 without its follow-up fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants