Skip to content

glob: treat a brace group with no comma as literal in match() - #32902

Open
robobun wants to merge 7 commits into
mainfrom
farm/183bdea2/glob-brace-no-comma-literal
Open

robobun wants to merge 7 commits into
mainfrom
farm/183bdea2/glob-brace-no-comma-literal

Conversation

@robobun

@robobun robobun commented Jun 27, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Bun.Glob.match() expands every {...} as a brace group, including groups that contain no comma:

new Bun.Glob("{a}").match("{a}")       // false, should be true
new Bun.Glob("{a}").match("a")         // true,  should be false
new Bun.Glob("{a}.ts").match("a.ts")   // true,  should be false
new Bun.Glob("{}").match("{}")         // false, should be true
new Bun.Glob("x{}y").match("xy")       // true,  should be false

bash only performs brace expansion when the group contains at least one unescaped comma (or a .. sequence expression): touch '{a}.ts'; echo {a}.ts prints {a}.ts. picomatch and minimatch agree:

require('picomatch')('{a}.ts')('{a}.ts')  // true
require('picomatch')('{a}.ts')('a.ts')    // false
require('minimatch')('{a}.ts', '{a}.ts')  // true

This bites framework filename conventions that use literal braces in paths, and means {1..3} (which Bun does not implement as a range) matches the wrong thing.

Cause

match_brace in src/glob/matcher.rs tries each comma-separated segment of a {...} group as a branch; when there is no comma it still tries the single segment between { and }, so {a} becomes a one-branch expansion matching a and {} matches the empty string.

Fix

Scan the pattern once up front and, for every {...} group that has no unescaped top-level comma (or no closing } at all), escape the braces to \{ / \}. The matching loop then sees them as ordinary literal bytes with no changes to its state machine. The scan uses the same depth / [...] / \ rules as match_brace, so a comma nested inside an inner group or inside a bracket class does not count, and only the braces themselves become literal: glob syntax inside the group still applies ({*} matches {abc}, {a{b,c}} matches {ab} or {ac}, same as bash and picomatch).

One deliberate divergence from match_brace: the scan only enters bracket mode at a [ when an unescaped ] follows, so an unclosed [ is a literal byte and cannot hide the group's } or ,. Without this, {a,[} would be misclassified as an unclosed group and go literal, but bash, picomatch, and minimatch all keep it an expansion, and match_brace already matched the a branch (it fires each branch at its comma and does not need the structural close). This only decides whether the group survives escaping; the [ branch itself is still unreachable because match_brace's own unclosed-[ handling is unchanged, which is the separate pre-existing gap #32895 addresses. The first false from the lookahead is sticky (no ] after one [ means none after any later [), so a long run of unclosed [ stays linear.

The escaped bytes stay inside r#match and never reach GlobWalker::build_pattern_components (which treats \ as a path separator on Windows and would split the pattern at the inserted escapes). Bun.Glob still caches the escaped form in a dedicated field so match() does not rescan on every call, but the walker keeps reading the raw pattern and its per-component r#match applies the escaping itself. Every other caller of bun_glob::r#match (test scanner, pm pack, --filter, path.matchesGlob, etc.) gets the scan inline.

The shell neutralized a component-leading interpolated ! by emitting the one-branch group {!}, which this change correctly turns into a literal. It now emits {!,!}, a real two-branch expansion whose branches are both the literal !, so interpolated ! stays inert.

Verification

New brace groups without a comma are literal block in test/js/bun/glob/match.test.ts covers the reported cases plus {1..3}, unclosed {, escaped \,, comma inside [...], glob syntax inside a literal group ({*}, {?}, {[ab]}), an unclosed [ inside a comma-bearing group ({a,[} still expands), literal-outer/expanding-inner nesting, wildcard and negation interaction, and already-escaped \{. A few existing assertions in the nested braces block were asserting the expansion behaviour for comma-less inner groups and are updated to the bash/picomatch result.

The deeply-nested-brace fixtures from 8c87871 were written against patterns that this change now treats as literals, so they no longer reached the depth counters they guard. The 300-level fixtures now carry a comma at every level so skip_branch still scans past the >255 boundary, and a comma-bearing 40,000-level fixture sits alongside the original unclosed one so match_brace's pre-scan still climbs past i16::MAX. Verified by temporarily narrowing that counter back to i16: the new fixture aborts with a checked-overflow panic and passes again at i32.

The braces test ran its 10-pattern x 7894-path vscode fixture matrix as a single test, which takes 4 to 5.3 seconds under bun bd (debug + ASAN) against the 5 second per-test budget and was timing out about one run in four, before and after this change. It is split into one test per pattern with identical assertions so each has its own budget.

bun bd test test/js/bun/glob/match.test.ts
# 37 pass, 0 fail

bun bd test test/js/bun/glob/scan.test.ts
# 172 pass, 0 fail

bun bd test test/js/bun/shell/bunshell.test.ts -t "glob"
# 14 pass, 0 fail

scan() / scanSync() pick up the fix via the walker's per-component r#match (new Bun.Glob("{a}.ts").scanSync() now yields {a}.ts, not a.ts), and path.matchesGlob inherits it through Bun.Glob.

Related: #32894 handles the unclosed-{ case specifically; this PR's preprocessing covers that case as well (an unclosed { has no top-level comma before a matching }, so it is escaped), so whichever lands second will need a small rebase in matcher.rs.

A brace group only expands in bash, picomatch, and minimatch when it
contains at least one unescaped top-level comma. Bun's matcher expanded
every {...} regardless, so {a} matched "a" instead of "{a}", {} matched
the empty string, and {1..3} matched "1..3".

Fix by scanning the pattern once in r#match and escaping each { / }
belonging to a group with no top-level comma (or no closing brace at
all) to \\{ / \\}; the matching loop then handles them as ordinary
literals with no changes. Bun.Glob runs the scan once at construction
and stores the result so match() stays on the fast path.
@robobun

robobun commented Jun 27, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:14 AM PT - Jun 28th, 2026

❌ @robobun, your commit b952eaf has 4 failures in Build #66133 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 32902

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

bun-32902 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. glob: treat an unclosed { as a literal in match() #32894 - Both fix literal brace handling in match() within src/glob/matcher.rs; glob: treat a brace group with no comma as literal in match() #32902's escape_literal_braces preprocessing also covers the unclosed-{ case that glob: treat an unclosed { as a literal in match() #32894 fixes, so only one can land cleanly

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds escape_literal_braces to the glob matcher to rewrite brace groups that lack a top-level comma into escaped literals before matching. A new match_preprocessed entrypoint skips re-scanning when the pattern is already preprocessed. Glob.constructor stores the escaped pattern; Glob.matchString calls match_preprocessed. Tests are updated to match the new literal-brace semantics.

Changes

Glob literal brace preprocessing

Layer / File(s) Summary
escape_literal_braces and match_preprocessed core
src/glob/matcher.rs, src/glob/lib.rs
Adds escape_literal_braces that scans a glob and rewrites brace groups lacking a top-level comma into escaped literals, returning None when no rewriting is needed. Adds match_preprocessed as a public entrypoint that assumes brace groups are already valid expansions. Updates r#match to delegate through both. Re-exports both new functions from lib.rs.
Glob API wiring
src/runtime/api/glob.rs
Glob::constructor runs escape_literal_braces on the input pattern bytes before storing them. Glob::r#match switches from bun_glob::r#match to bun_glob::match_preprocessed to avoid redundant re-scanning on each match call.
Test updates
test/js/bun/glob/match.test.ts
Updates the braces pattern to use trailing-comma alternatives, reworks nested-brace edge-case assertions to reflect literal treatment of comma-free groups, adds a dedicated brace groups without a comma are literal test suite, and adjusts deep unclosed-brace expectations to match the new preprocessing behavior.

Possibly related PRs

  • oven-sh/bun#31220: Directly modifies src/glob/matcher.rs brace-handling to prevent commas inside [...] from acting as brace separators, the same layer this PR extends with literal-brace preprocessing.
  • oven-sh/bun#31272: Wraps shell patterns in {!} brace groups to neutralize !; the literal-brace preprocessing added here directly affects how such comma-free brace groups are interpreted by the matcher.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly states the main change: treating comma-less brace groups as literals in glob matching.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is complete and on-topic, covering the problem, fix, and verification, though it uses custom headings instead of the template.

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

@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

🤖 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/glob/matcher.rs`:
- Around line 116-118: Clippy is flagging the early-return in the glob matcher
because the result of strings::index_of_char is only used as a presence check
and the index is discarded. Update the logic in the glob matching path around
the index_of_char call to use the ? operator for the None case instead of an
explicit is_none() return, since the later parsing step rescans the string
anyway. Keep the change localized to the matcher function that handles brace
detection.

In `@test/js/bun/glob/match.test.ts`:
- Around line 336-434: The new brace-group tests only verify literal braces, not
that the entire no-comma group is treated as plain text. Add at least one case
in match.test.ts using a metacharacter inside a no-comma group, such as
Glob("{*}") or Glob("{[ab]}"), and assert it matches the exact literal string
and does not act like a glob. Keep the coverage alongside the existing
brace-group tests in the Glob.match suite.
🪄 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: 1968c629-7baa-46a5-9e82-6060d4374dd6

📥 Commits

Reviewing files that changed from the base of the PR and between 8706328 and 3a77fc6.

📒 Files selected for processing (4)
  • src/glob/lib.rs
  • src/glob/matcher.rs
  • src/runtime/api/glob.rs
  • test/js/bun/glob/match.test.ts

Comment thread src/glob/matcher.rs Outdated
Comment thread test/js/bun/glob/match.test.ts
Comment thread src/runtime/api/glob.rs Outdated
Comment thread test/js/bun/glob/match.test.ts Outdated
Comment thread src/glob/matcher.rs
The previous commit stored the brace-escaped pattern in Glob.pattern,
which GlobWalker::build_pattern_components also reads. On Windows it
treats backslash as a path separator, so the inserted escapes would
split the pattern into bogus components. Keep Glob.pattern raw and
cache the escaped form in a separate match_pattern field used only by
match(); the walker's per-component r#match applies the escaping itself.

The shell neutralized a component-leading interpolated ! with the
one-branch group {!}, which the matcher now treats as a literal.
Emit {!,!} so the group has a top-level comma and still expands.

Also: clippy question_mark fix, rebuild the deeply-nested-brace fixture
with a comma at every level so it still reaches the >255 depth counters,
and cover glob syntax inside a literal group ({*}, {?}, {[ab]}).
@robobun

robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Re the duplicate flag: #32894 and this PR are not the same fix. #32894 only handles an unclosed {; this PR handles the general comma-less brace group ({a}, {}, {1..3}), of which the unclosed { is one case, so the preprocessing here subsumes it. They do conflict in src/glob/matcher.rs, so whichever lands second needs a small rebase. The PR body notes the same.

Also worth calling out for reviewers: the first push of this PR broke the Bun shell's glob neutralizer. neutralize_glob_metachars wrapped a component-leading interpolated ! in the one-branch group {!}, which this change correctly turns into a literal, and bunshell.test.ts caught it across every CI lane. It now emits {!,!} instead, a real two-branch expansion whose branches are both the literal !. The Windows walker concern from the inline review is also addressed: the escaped pattern no longer reaches GlobWalker::build_pattern_components.

Comment thread test/js/bun/glob/match.test.ts
…es fixture test

The 40,000-brace fixture from 8c87871 guarded match_brace's pre-scan
counter against being narrowed back to i16. With comma-less groups now
escaped to literals before matching, that unclosed pattern never reaches
match_brace, so add a comma-bearing 40,000-level variant alongside it
(verified by narrowing the counter to i16, which aborts with a checked
overflow on the new fixture and passes without it).

The braces test ran its 10-pattern x 7894-path vscode fixture matrix as
one test, which takes ~4-5.3s under a debug+ASAN build against a 5s
per-test budget and was timing out roughly one run in four on this PR.
Split the matrix into one test per pattern with identical assertions so
each has its own budget; the remaining direct assertions stay in the
original braces test. No coverage is removed.

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

All four issues I raised earlier are now addressed (the walker keeps the raw pattern, the shell emits {!,!}, and both the >255 and >32767 depth fixtures are restored), and I didn't find anything new — but this is a deliberate semantic change to bun_glob::r#match that fans out to every caller (walker components, --filter, pm pack, path.matchesGlob, the shell), so it's worth a human sign-off on the behavior change itself.

Extended reasoning...

Overview

This PR changes Bun.Glob.match() (and every other bun_glob::r#match caller) to treat {...} groups with no top-level unescaped comma as literal braces rather than one-branch expansions, aligning with bash/picomatch/minimatch. It adds escape_literal_braces() preprocessing in src/glob/matcher.rs, a match_preprocessed() fast path, caches the escaped pattern in Glob (src/runtime/api/glob.rs) separately from the raw pattern the walker uses, updates the shell's ! neutralizer in Expansion.rs to emit {!,!}, and adds/reworks ~150 lines of test assertions in match.test.ts.

Security risks

None identified. No auth/crypto/permissions surface; the change is pure pattern-matching semantics on byte slices. The preprocessing allocates at most glob.len() + literal.len() bytes and uses SmallVec for the scan stack, so no new unbounded-growth vector.

Level of scrutiny

Medium-high. The implementation itself is compact and well-tested, but r#match is called from many places — the walker's per-component slow path, the test scanner, pm pack, workspace --filter, path.matchesGlob, and the shell glob — and this PR intentionally changes its result for inputs like {a}, {}, {1..3}. The first push broke bunshell.test.ts on every CI lane (the {!} wrapper) and would have regressed Windows scan() (escaped \ treated as a path separator); both are now fixed, but they illustrate the blast radius. A maintainer should confirm the semantic change is wanted across all those call sites, not just Bun.Glob.match().

Other factors

  • All four of my earlier inline comments are resolved in the current diff: match_pattern is stored separately so the walker still sees the raw pattern; Expansion.rs emits b"{!,!}"; the 300-level fixtures use "{a," so skip_branch's counter is exercised; and 9eebde1 adds a 40,000-level comma-bearing fixture to keep match_brace's i32 pre-scan covered.
  • The bug-hunting pass on the latest revision found nothing.
  • There is a known textual conflict with #32894 in matcher.rs (this PR subsumes its unclosed-{ handling).
  • Test coverage for the new behavior is thorough (literal groups, nested literal-outer/expanding-inner, escaped commas, commas inside [...], glob syntax inside literal groups, negation, wildcard interaction, already-escaped braces).
  • The braces-vs-filelist test was also refactored from one ~79k-match test into test.each to avoid the 5s ASAN budget — a reasonable de-flake but worth a quick glance since it changes test structure unrelated to the headline fix.

Build 65889 produced no signal: every build-cpp and build-rust job
expired waiting for a Buildkite agent and the 252 downstream test jobs
cascaded to waiting_failed, so nothing compiled or ran.
Comment thread src/glob/matcher.rs
escape_literal_braces set in_brackets on every [ and never cleared it
when the class was unclosed, so in a pattern like {a,[} the closing }
was swallowed and the end-of-input drain escaped the opening { even
though the group has a top-level comma. That made the group a dead
literal and regressed new Glob("{a,[}").match("a") from true to false.
bash, picomatch, and minimatch all expand {a,[} to a and [.

Only enter bracket mode when an unescaped ] exists after the [, which
is also how picomatch treats an unclosed [. The match_brace scanners
are unchanged: they fire each branch at its comma and never needed the
structural close, so the pre-existing handling of an unclosed [ before
a comma is out of scope here.
Comment thread test/js/bun/glob/match.test.ts Outdated
Comment thread src/glob/matcher.rs Outdated
bracket_has_closing walks from the [ to the end of the pattern, and the
escape_literal_braces loop called it for every [ that was not already
inside a class. A run of unclosed [ after a { therefore rescanned the
remaining pattern once per [, making the scan quadratic where it used
to be linear. The lookahead and the loop use the same escape-aware
walk, so once it finds no ] after one [ there is none after any later
[ either; a single flag ends the probing and restores the linear scan.
Measured 2.6s down to 71ms on a { followed by 40,000 unclosed [ under
a debug ASAN build, with identical results, so no regression test is
added (it cannot be asserted without coupling the test to a timeout).

Also narrow the bracket_has_closing doc and the matching test comment:
the references expand both branches of {a,[}, but this scan only keeps
the group an expansion; whether the [ branch can match is still up to
match_brace, whose unclosed-[ handling is a separate pre-existing gap.

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

All my earlier feedback is addressed (b952eaf handles the last two — the O(n²) unclosed-[ rescan and the overstated {a,[} comments). No further issues found, but this is a user-visible semantic change to the glob matcher that ripples through the walker, the shell's metachar neutralizer, pm pack, --filter, and path.matchesGlob, so it warrants a human sign-off.

Extended reasoning...

Overview

This PR changes Bun.Glob.match() (and every other caller of bun_glob::r#match) so that a {...} group with no unescaped top-level comma is treated as literal braces rather than a one-branch expansion, matching bash/picomatch/minimatch. It does so by adding a preprocessing pass (escape_literal_braces) that rewrites such groups to \\{...\\}, plus a match_preprocessed entrypoint so Bun.Glob can cache the rewrite. The escaped form is deliberately kept out of GlobWalker::build_pattern_components (which treats \\ as a path separator on Windows) by storing it in a separate match_pattern field while the walker keeps reading the raw pattern. The shell's neutralize_glob_metachars is updated from {!} to {!,!} so its component-leading-! wrapper survives the new preprocessing. ~120 lines of new Rust plus extensive test additions and rewrites in match.test.ts.

Security risks

None identified. Glob patterns at every call site are developer-authored (CLI flags, package.json fields, test config, shell literals), not remote-attacker-controlled. The one algorithmic-complexity concern I raised (O(n²) rescans on long runs of unclosed [) was fixed in b952eaf with the no_closing_bracket latch, restoring linear behaviour.

Level of scrutiny

High. This is a deliberate user-visible semantic change to a core matching primitive that fans out to Bun.Glob.match/scan, path.matchesGlob, the shell glob path, bun pm pack files[], --filter, and the test scanner. The implementation has subtle interactions (Windows \\-as-separator in the walker, bracket-class vs. brace nesting, unclosed-[ handling, depth-counter test coverage) and went through six rounds of fixes during review — each of which the author handled well, but the breadth of impact and the interaction with two sibling PRs (#32894, #32895) make this one a human should sign off on.

Other factors

All seven of my prior inline findings are now addressed in code: the Windows walker regression (raw pattern kept for the walker), the shell {!} wrapper (now {!,!}), the >255 and >32767 depth-counter test coverage (rebuilt with comma-bearing fixtures, verified by re-narrowing the counter), the unclosed-[-hides-} regression (bracket_has_closing lookahead), the O(n²) rescan (latched), and the overstated doc/test comments (narrowed). The bug-hunting pass on the current head found nothing further. The robobun CI comment still shows ❌ against 17de434 (the second-to-last commit); the head is b952eaf, so CI status on the final commit is worth confirming before merge.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI status at head b952eafede (build 66133)

The diff is green. The build is red, but every failure is CI infrastructure or an unrelated network test, and two of the failing lanes ran this PR's test file and passed it.

283 jobs: 281 passed, 4 failed, 1 timed out. The complete set of red lanes:

Lane What failed Related to this PR?
darwin 26 aarch64 test-bun (x2) buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'. The jobs never received a binary; zero tests ran. No (Buildkite infra)
darwin 14 aarch64 test-bun (timed out) test/js/bun/http/fetch-file-upload.test.ts uploads roundtrip with sendfile() (10s timeout, 4 retries) and test/js/bun/http/bun-serve-file.test.ts aborting a streaming file response mid-transfer ... (90s timeout). No (HTTP / sendfile)
debian 13 aarch64 test-bun test/js/bun/net/socket.test.ts expectMaxObjectTypeCount leak assertions (Expected: <= 50, Received: 58) plus Unexpected open(). No (TCP object-count flake)
alpine 3.23 x64 test-bun test/js/sql/sql-mysql.auth.test.ts: the mysql_native_password Docker sidecar never passed its health check (application not healthy after 1m0s), plus an http-proxy tunnel timeout. The same MySQL sidecar failure hit this PR's earlier build 65783. No (Docker service infra)

There is also one warning-level annotation for test/js/bun/http/serve.test.ts on Windows 2019 x64 that passed on retry.

None of those files or subsystems (TCP sockets, Docker MySQL, HTTP file serving, sendfile) is touched here. This PR changes src/glob/matcher.rs, src/glob/lib.rs, src/runtime/api/glob.rs, src/runtime/shell/states/Expansion.rs, and test/js/bun/glob/match.test.ts.

More useful than the absence of glob failures is their presence as passes in the red lanes themselves:

  • the darwin 14 aarch64 shard ran test/js/bun/glob/match.test.ts and test/js/bun/shell/bunshell.test.ts; neither appears in its failures.
  • the alpine 3.23 x64 shard also ran test/js/bun/glob/match.test.ts; it is not in its failures either.

So the changed test file ran and passed on both a failing macOS aarch64 lane and a failing Linux x64 musl lane, on top of 281 fully green jobs and zero glob, shell, Bun.Glob, path.matchesGlob, or fs.glob failures anywhere in the build.

For the build history on this branch: build 65889 produced no signal at all (every build-cpp / build-rust job expired waiting for a Buildkite agent and the 252 downstream test jobs cascaded to waiting_failed), so I re-ran it once with an empty commit. Build 66133 is that re-run. I am not going to keep pushing empty commits to re-roll network and Docker flake, so this is where it stands.

Local verification on this exact head under bun bd (debug + ASAN): test/js/bun/glob/match.test.ts 37/37, test/js/bun/glob/scan.test.ts 172/172, test/js/bun/shell/bunshell.test.ts -t glob 14/14, and the new tests fail on an unfixed build. All review threads (9) are resolved.

This is ready for a maintainer: the remaining red is infrastructure and unrelated flake that a retrigger will not reliably clear.

@robobun

robobun commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

The Bun shell shows this matcher bug directly. With files {x}.a.txt and x.a.txt, Bun.$\echo {x}.*.txt`printsx.a.txton main. bash prints{x}.a.txt`. I tried a shell side fix in #39634 and closed it: this PR is the right layer and fixes every shell case as well.

The shell cases are in test/js/bun/shell/brace.test.ts at 6759e13 (describe "a literal brace group globs as literal text", nine tests, eight fail on main). They cover {x}.*, {x}*, *.{x}, {x}/*, an unclosed {a,b*, {a,b}x{c*, {x},*, {a,{x}}.* and {a,b},*. All nine expected outputs were checked against bash 5.2. They should pass unchanged on this branch and would give it shell level regression coverage.

One related note from that attempt: the walker does not follow a symlink for a segment it classifies as a pattern. {x} contains {, so echo {x}/* still fails when {x} is a symlink to a directory, before and after this PR. That is a separate walker gap, not something this PR needs to solve.

This branch has not been deployed

No deployments
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.

1 participant