Skip to content

linux: a wrap that starts over keeps what it has walked, and ends - #638

Merged
ronleizrowice-ant merged 1 commit into
mainfrom
fix/linux-wrap-restart-keeps-walks
Oct 2, 2026
Merged

ronleizrowice-ant merged 1 commit into
mainfrom
fix/linux-wrap-restart-keeps-walks

Conversation

@ronleizrowice-ant

Copy link
Copy Markdown
Collaborator

Follows #628, which is on main and in no release. Since #628 a wrap walks its read patterns in turns, and starts over when updateConfig() replaced the configuration meanwhile. It started over from nothing: every replacement during a wrap cost a whole walk more, identical content included, and nothing bounded it. A host that refreshes its configuration often could keep a command from starting.

What changes

  • A wrap keeps what it found. The directories it listed and each pattern's finished walk stay with it when it starts over, so only a pattern new to it is walked. What depends on the configuration is worked out again each time.
  • It ends. An attempt with nothing new to walk takes no turn of the event loop. After two restarts a new pattern is walked without a turn as well.
  • "One wrap, one configuration" stands: the check is unchanged, and a deny added mid-walk applies.

Who sees a difference

Only a caller whose configuration is replaced during a wrap. What the wrap keeps is as old as its first walk, so a path made since is not seen, as with one long walk. A third attempt holds the thread while it walks a new pattern (about 0.1 s here).

Measured

Six ** patterns over 400,000 files, through the package's export. Wrap time as a multiple of an undisturbed wrap (about 1 s), on Node 22 / Bun 1.4.2:

updateConfig() during the wrap main here
same content, once 2.1 / 2.2 1.0 / 1.0
same content, every 250 ms 6.9 / 6.5, ending when the updates stop 0.8 / 1.1
a new pattern, every 150 ms 6.3 / 8.7 1.0 / 1.3

Testing

Five new tests. Each fails with its part of the change taken out (nine mutations), and all five fail on main's source. Linux full suite: nothing that passes on main fails here.

A wrap starts over when updateConfig() has replaced the configuration
during its walk. It started over from nothing, so each replacement cost a
whole walk more, the same content included, and nothing bounded it.

The wrap now keeps the directories it listed and each pattern's finished
walk, so only a pattern new to it is walked. After two restarts it walks
without giving the thread back.

@antdres antdres left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

/stamp

@ronleizrowice-ant
ronleizrowice-ant merged commit c7adb1e into main Oct 2, 2026
16 checks passed
@ronleizrowice-ant
ronleizrowice-ant deleted the fix/linux-wrap-restart-keeps-walks branch October 2, 2026 16:52

@antdres antdres left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One blocker, found after merge. It is on main (c7adb1e) and in no release yet.

A deny rule added while a command is being wrapped can miss a file that already existed when the rule was added.

Steps, on Linux with real bubblewrap:

  1. The configuration denies proj/**/.env. A wrap of cat proj/key.pem starts on a large tree (66,000 entries, so the walk takes a few turns).
  2. 30 ms in, the host writes proj/key.pem, then calls updateConfig() with a configuration that also denies proj/**/*.pem.
  3. The wrap starts over, as intended, and returns.
result of cat proj/key.pem
before this PR (60e5e9f) Permission denied
this PR (c7adb1e) prints the file's contents
this PR, same configuration, wrap not disturbed Permission denied

Same result on every run (3 of 3 each side).

Why: when the wrap starts over, the new *.pem pattern is walked from the directory listings the first attempt kept. proj/ was listed before key.pem was written, so the new rule never sees the file. Before this PR the second attempt listed the disk again.

This is not the "as old as one long walk" case from the description. The rule was added after the file existed, and the wrap that returns claims to apply that rule.

The same happens when the configuration is replaced with identical content: a file written between the first walk and the replacement is readable here and denied before.

A possible fix: keep the part that makes the wrap end (walk without turns after two restarts), and have an attempt that starts over read the disk again instead of reusing the first attempt's listings and walks.


Generated by Claude Code

ronleizrowice-ant added a commit that referenced this pull request Oct 2, 2026
…bundle

The kept walks go into the one options object, beside the anchor, the
budget and the listings. Three things git did not mark: the anchor is part
of a kept walk's key; a kept walk is charged to the budget the entries it
looked at; and the allow side passes its anchor on as well as the walks.
The budget is still made anew by a wrap that starts over.
ronleizrowice-ant added a commit that referenced this pull request Oct 2, 2026
It undoes the join of #638: the kept walks, the anchor in their key and
their charge to the budget go with the walks themselves. The budget and
the listings are again made by each attempt. Of the three join cases one
stays, under another name: a budget that arrives during the denyRead walk
is the one the wrap ends by.
huangyingting pushed a commit to repomesh/sandbox-runtime that referenced this pull request Oct 4, 2026
Since anthropics#638 a wrap that starts over kept the directories it had listed and
each pattern's finished walk. A host that wrote a file and then installed
a rule denying it, while a command was being wrapped, got a command that
could read the file: the rule was walked over a listing made before the
file was there.

A wrap that starts over now keeps nothing, so what it hands out was read
after its configuration was installed. It still ends: after two restarts
it walks without giving the thread back. And an attempt is left as soon as
its configuration is seen to be replaced, not walked to its end.
@ronleizrowice-ant ronleizrowice-ant mentioned this pull request Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants