Skip to content

Fix berry workspace globs with regex groups (#1490) - #1493

Merged
Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-berry-workspace-glob-grammar
Oct 13, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-berry-workspace-glob-grammar

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1490

Summary

A hosted scan / get run from a yarn berry workspace member now
refuses with redirect_workspace_lockfile_elsewhere, naming the root,
when the root's workspaces reaches the member through picomatch-only
syntax. Before this fix the run pinned nothing and exited 0, and the
next yarn install at the root installed the unpatched package.

Root cause

utils/workspace_globs.rs is the matcher that decides workspace
membership. Since #1480 it reads each root lock's dialect, but yarn
berry matches workspaces with fast-glob / picomatch, and the matcher
read the following picomatch syntax as literal text:

  • regex groups: packages/(a|b), packages/pkg-(a|b)
  • POSIX bracket classes: [[:alpha:]], [[:digit:]]x
  • .. segments: packages/a/../b

No dialect listed the member, so hosted/governing_root.rs treated it
as a standalone project with no lock.

Fix

WorkspacesDialect gets a picomatch flag, which is on only for yarn
berry. With it on:

  • Regex groups are rewritten into the extglob groups ExtglobMatcher
    already reads: (a|b) becomes @(a|b), (a|b)? becomes ?(a|b)
    and (a|b)+ becomes +(a|b). Likewise [ab]+ becomes +([ab]),
    and x+ inside a group becomes +(x). An alternative containing /
    is dropped, because it can never match a single path component.
  • A group without | is still a group only when fast-glob treats that
    brace alternative as dynamic, or when the pattern is a ! (ignore)
    pattern. Otherwise it is literal, as yarn reads packages/(a).
  • Brackets also accept POSIX classes, using picomatch's definitions.
  • A .. segment is resolved only through a static prefix. It is
    literal in ! patterns, so it excludes nothing, as fast-glob does.

extglob_group now skips [...] classes as whole units for every
dialect, so a ) or | inside a class no longer ends or splits the
group. Matching for npm, yarn classic, Bun, vlt, pnpm and uv is
otherwise unchanged. The redirect_workspace_lockfile_elsewhere row in
CLI_CONTRACT.md now documents the berry grammar.

Bugbot's review of 7d6a48a found two issues, both fixed in 3eef5c3:

  • A group whose alternatives hold / now matches across path
    components. This fails closed.
  • .. now resolves in a static pattern.

Later Bugbot and reviewer rounds were fixed in 267ed2f, 44fd58c and 18c5b35:

  • ?( and +( right after a group or class now open an extglob.
  • A trailing quantifier and an in-group + survive the expansion of
    a /-spanning group.
  • A ? or + that quantifies a /-spanning group now fails closed:
    it lists every directory, and as a ! pattern excludes none.

Bugbot's review of 18c5b35 is clean.

I ran /code-review high on the first pass. It found 10 issues, all
now fixed (commit 1543665), with one deliberate exception: a static
packages/(a) still lists a literal packages/(a) directory. The
review probed fast-glob 3.3.3, which lists nothing there. I kept the
broader reading because it fails closed: the worst case is a loud
refusal, never a silent exit 0.

Tests

Issue Test Without fix With fix
#1490 utils::workspace_globs::tests::yarn_berry_reads_picomatch_syntax (every row of the issue's matrix plus the review cases) FAILED ok
#1490 hosted::governing_root::tests::package_json_workspaces_read_in_the_root_locks_dialect (berry rows: (a|b), [[:alpha:]], a/../b refuse; !packages/(b|c) and static (b) do not) FAILED ok
#1490 in_process_redirect_pnpm::hosted_run_from_berry_picomatch_workspace_member_refuses (scan and get, exit 1, lock untouched) not run red (added after the fix; it drives the same matcher path as the governing-root rows) ok

Local gate on 7d6a48a (3eef5c3 re-ran the core workspace_globs and governing_root tests, in_process_redirect_pnpm, fmt and clippy, all clean): cargo fmt --all -- --check passed, and cargo clippy --workspace --all-features -- -D warnings passed with no warnings. cargo test --workspace --all-features --no-fail-fast passed everything except 13 tests, all of which fail for environment reasons. Twelve inject write failures with chmod, which the sandbox's root user bypasses: covgap_commands_vendor 3, in_process_redirect 3, repair 2 and core unit tests 4. The thirteenth, e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed, needs patches-api.socket.dev, which the sandbox proxy blocks. None of them touches workspace globs, and CI runs as non-root with network access.

🤖 Generated with Claude Code


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
Running socket-patch from a yarn berry workspace member whose root
lists it with a regex group (packages/(a|b)), a POSIX class
([[:alpha:]]) or a `..` segment (packages/a/../b) pinned nothing and
exited 0, so the next install at the root stayed unpatched. Yarn berry
matches workspaces with picomatch, which reads all three, while our
matcher read them as literal text and saw no workspace root.

The yarn berry dialect now reads that syntax the way picomatch does,
so the run is refused with redirect_workspace_lockfile_elsewhere and
names the root to run from. A group without `|` stays literal in a
pattern fast-glob treats as a plain path, as yarn reads it, and a
negated group now excludes the members it names. npm, yarn classic,
Bun, vlt, pnpm and uv keep their grammar.

Assisted-by: Claude Code:claude-opus-5-5
Review of the first pass found yarn berry patterns it still misread,
so a run from a member could again pin nothing and exit 0, or refuse
a directory yarn never lists:

- `(a|b)?`, `(a|b)+`, `[ab]+` and `(a+|c)`: picomatch reads `?` and
  `+` there as regex quantifiers.
- `(a|[)])`: a `)` or `|` inside a class closed or split the group.
- `packages/{(a),c}`: fast-glob decides static vs dynamic per brace
  alternative, so `(a)` stays a literal path there.
- `packages/*/../b` lists nothing, and `!packages/a/../b` excludes
  nothing: fast-glob resolves `..` only through a static prefix and
  never in an ignore.
- `(a|)` and `(a/b|c)`: an alternative is matched within one path
  component.

Regex groups are now rewritten into the extglob groups the matcher
already reads, instead of being spliced in as text, and extglob groups
skip classes whole for every package manager.

Assisted-by: Claude Code:claude-opus-5-5
Running `scan` or `get` in hosted mode from a yarn berry workspace
member that the root lists through a regex group, a `?` quantifier, a
POSIX class or a `..` segment must refuse with exit 1 and leave the
root lock untouched.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs
Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs
Bugbot on #1493 found two shapes that still pinned nothing and exited
0 from a yarn berry member:

- `(packages/ui|packages/core)`: a group alternative holding `/` was
  dropped, so neither member was listed. Such groups are now spelled
  out like brace sets and matched across path components.
- `packages/(a)/../b`: fast-glob reads a static pattern as a literal
  path, so its `..` resolves even after the `(a)` segment.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs Outdated
A `?(` or `+(` right after a regex group or a class was read as a
quantifier, so the extglob it opens matched literally. Members listed
by `packages/(a|b)?(c|d)`, `packages/(a|b)+(c)` or `packages/[ab]+(c)`
were then missed and the run exited 0 with nothing pinned, and the
same `!` pattern excluded nothing. Those characters now open the
extglob, as picomatch reads them.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

When a regex group with a `/` in an alternative was spelled out, a
`?` or `+` after it, and a `+` inside it, were left as plain text.
Members listed by `(packages/a|apps/b)?x` or `(packages/a+|apps/b)`
were then missed and the run exited 0 with nothing pinned. A `?` now
also yields the spelling without the group, a `+` keeps one
occurrence, and each alternative is read as inside a group.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs
Comment thread crates/socket-patch-core/src/utils/workspace_globs.rs
A `?` or `+` after a yarn berry regex group with a `/` in an
alternative skips or repeats a path separator, which can't be matched
component by component. Approximating it either missed members
(the run exited 0 with nothing pinned) or listed directories yarn does
not. Such a pattern now fails closed: it lists every directory, so a
run from under that root is refused, and as a `!` pattern it excludes
nothing.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 18c5b35. Configure here.

Merged via the queue into main with commit ee8f662 Oct 13, 2026
54 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-berry-workspace-glob-grammar branch October 13, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants