Repository navigation
Brackets are read in linear time, and a read rule is compiled once - #718
Merged
ronleizrowice-ant merged 2 commits intoOct 16, 2026
Merged
ronleizrowice-ant merged 2 commits into
ronleizrowice-ant merged 2 commits into
Conversation
getFsWriteConfig() is a getter that callers reach from permission checks, once a path. Under read rules that hold many brackets one call took up to 300 ms. globToRegex() split its pattern with /(\[[^\]]*\])/, which looks for a `]` from every `[` anew: the square of the length on a run of `[` that none closes. It now goes through the pattern once, for the same parts. Every read rule was compiled for each answer. What a rule compiles to is now kept by its normalized pattern, which is all it depends on. What is asked of the configuration and of the disk is asked every time, so the answers are what they were.
Glob syntax was read in two places, each in its own way: globToRegex() by a chain of replacements, the walk by a tokenizer of its own. Now parseGlob() goes through a pattern once and gives typed tokens, and both are built from those. Four spellings read differently. A * or ? between brackets is a member of the set. Brackets with nothing in them are text. So is every [ that nothing closes. A name that spells __GLOBSTAR__ is that name. The walk can follow each of these a name at a time, so it lists them through symlinked directories like any other pattern. The same meaning in other text: a ] outside a set is written \], and wildcards side by side are kept to the fewest that match the same.
Collaborator
Author
|
/approve 069588c |
ronleizrowice-ant
enabled auto-merge (squash)
October 16, 2026 15:59
antdres
approved these changes
Oct 16, 2026
antdres
left a comment
Collaborator
There was a problem hiding this comment.
No blockers found at 069588c (Linux, bubblewrap, bun 1.4.2; macOS and Windows not run here).
- Speed-up is real: one
getFsWriteConfig()call with 96 KB of[in a read rule takes 3.6 s on the base under bun and 3 ms here; 16 KB goes from 109 ms to 1.3 ms. Ordinary rules are no slower (20 globs 0.1 ms, 1,000 globs 13 ms -> 10 ms). - Same answers as the base:
getFsWriteConfig(),getFsReadConfig()and the bwrap command are byte-identical for 9 configurations. The Linux deny expansion and the raw walk agree on 600 random trees with 2,700 symlinks (8,400 expansions, 16,800 walks, 0 differences). The macOS profile text for 17 glob rules differs only where**/**/xis now written**/x. - The new parser matches what the base matches: 300,000 random patterns, 8.4 million pattern-and-path pairs, 0 differences outside the three kinds below.
- Live sandbox:
**/.env, a/**directory deny and a folder named[user-id]are denied on base and here; writes land only where allowed. - Merged into today's main (five PRs landed after this branch's CI ran): it merges clean, typechecks and builds, and the sandbox test suite fails the same 41 tests as main alone (container-related), with 123 more passing.
Not blocking, but the description does not say it: the second commit changes how three kinds of pattern are read, compared with main.
- A wildcard inside brackets is now a member of the set:
[r*]eadme.txtdeniesreadme.txthere and did not on main (seen live). - Empty brackets are text:
list[].tsmatches the file of that name; on main it matched nothing. - Two or more
[that nothing closes are text; on main they matched odd strings.
In each case main's reading was not something anyone would rely on, and the README is updated, but it belongs in the 0.0.80 notes.
Generated by Claude Code
Merged
ronleizrowice-ant
deleted the
perf/brackets-in-linear-time-and-a-read-rule-compiled-once
branch
October 16, 2026 16:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
getFsWriteConfig()is a getter that callers reach from permission checks, once a path. Under read rules that hold many brackets, one call takes up to 300 ms on main, where 0.0.79 takes 4.Why
globToRegex()split its pattern with/(\[[^\]]*\])/, which looks for a]from every[anew: the square of the length on a run of[that none closes. A profile has 77 % of the getter's time there. It came with Read brackets that spell no set of characters as text, so no path entry keeps a sandbox from starting #687 and is in no release.What changes
splitAtBracketExpressions()goes through the pattern once, for the same parts. Every caller ofglobToRegex()gains, a wrap among them.compiledDenyGlob()keeps what a read rule compiles to, by its normalized pattern, which is all that depends on. Past a million characters of pattern text, all that is kept is let go.rel/*) throwsENOENT, as a relative name does, and as both do in 0.0.79. On main thetryround the compile took in the step before it, and such a pattern counted as one that covers.Keeping the whole answer would be faster still, and I tried it. It lags behind the disk: where an
allowReaddirectory is replaced by a link, the entry stops counting at once today, and would go on counting until something refreshed the answer. A test now holds that.Measured
Node 22, milliseconds.
[[[a][z-a], a credentials deny[[Bun 1.4.3 gives the same picture. Memory over 4,000 distinct patterns of 3.9 KB levels off where main's does (250 MB). With nothing let go it is at 352 MB by then and rising.
Testing
globToRegex()anddenyGlobRegex()on 300,000 generated bracket patterns and 20 edge ones: none differs.getFsWriteConfig(),getFsReadConfig()andwrapWithSandbox()on 160 generated configurations, on Node 22, Bun 1.4.2 and Bun 1.4.3: the same byte for byte, the session's token, its ports and the helper's path set aside.globToRegex()against a reference matcher fails 38 tests on a wrong scan.