Skip to content

🔓 fix: Let Linked-Worktree Lanes Commit When the Checkout Lives in the Worker Home - #274

Merged
danny-avila merged 2 commits into
mainfrom
danny-avila/lane-srt-writes
Sep 29, 2026
Merged

danny-avila merged 2 commits into
mainfrom
danny-avila/lane-srt-writes

Conversation

@danny-avila

@danny-avila danny-avila commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A linked-worktree lane (#270) could not commit when its checkout sat inside the worker's home directory, which is where a personal machine keeps its repositories. git add failed with Unable to create '.git/worktrees/<name>/index.lock': Read-only file system, and ref updates failed the same way. The Skynet worker canary hit this on its first lane commit, so the worker was rolled back.

The cause is how the sandbox runtime rebuilds a read-denied directory on Linux. The worker's home is denyRead, so bwrap mounts a tmpfs over it and then restores the granted paths: write binds first, then read binds. A lane is granted read access to the whole common .git and write access to objects, refs, logs/refs, lfs and its own worktrees/<name>. The read-only bind of .git therefore landed on top of those write binds and masked them. Checkouts outside the home directory were unaffected, which is why the fake-manager tests passed.

On Linux, each existing writable Git directory is now also listed as a deeper read-deny. SRT processes read-denies shallowest first and re-binds each one's write paths, so these deeper entries restore the write binds after the ancestor read bind. The common directory stays a live, read-only host directory. A regression test runs the real sandbox in both layouts, and a new Linux CI job runs it with bubblewrap.

Related: #268, #270, #272.

How it works

Mount order inside the worker home, with this change (Linux):

--tmpfs  ~                         read-deny: worker home
--bind   ~/…/.worktrees/task-a     lane root (write)
--ro-bind ~/…/.git                 common Git directory (read)       ← used to be the last word
--tmpfs  ~/…/.git/objects          deeper read-deny ...
--bind   ~/…/.git/objects          ... re-binds the write path on top
--tmpfs/--bind ~/…/.git/refs, logs/refs, worktrees/task-a   (same)

Only directories that exist are listed. Git creates neither lfs nor logs/refs inside a lane, which matches the behavior outside the home directory. Nothing changes on macOS, where Seatbelt applies subpath rules instead of bind mounts.

This keeps the common directory a real directory rather than a tmpfs placeholder, which matters for two reasons:

  • No top-level files. A lane cannot create anything at the top of .git, for example packed-refs during git pack-refs or a MERGE_HEAD, so a Git transaction can never split between vanishing tmpfs files and host-backed refs.
  • No stale reads. Files that Git replaces by rename, such as packed-refs or config, are seen live, because the whole directory is bound rather than individual files.

Testing

  • Real sandbox reproduction (WSL, bubblewrap 0.6.1, SRT 0.0.75):
    • Before this change, a lane under $HOME fails git add on index.lock and git branch on the ref lock.
    • With it, the lane commits and branches.
    • Writes to the checkout's HEAD, index, config and MERGE_HEAD, and to sibling metadata, fail with EROFS in both layouts.
  • New linked-worktrees-live.test.ts runs the real sandbox, gated on LIBRECHAT_CODE_LIVE_SRT_TESTS=1, with the checkout both beneath and outside the worker home:
    • In a lane it commits, branches, tries to tamper with eight protected files, and runs git pack-refs --all.
    • It then checks that every branch still resolves on the host and that the protected files are byte-for-byte unchanged.
    • Both cases pass. With the re-bind disabled, the home case fails with the Skynet error.
  • New CI job Linux Native Sandbox Tests installs bubblewrap, lifts Ubuntu 24.04's AppArmor restriction on unprivileged user namespaces, and runs the live test.
  • native-sandbox.test.ts checks that the writable Git directories become deeper read-denies on Linux only, and that a missing lfs is not listed.
  • packages/code: tsc is clean, and npm test shows no new failures.

…d-denied home

A read grant on the whole common Git directory masked the write binds
beneath it whenever the checkout lived inside the worker home, which SRT
re-binds under a tmpfs (writes first, then reads). Lanes could not create
their own index.lock or ref locks. Grant each entry off the writable paths
instead, and add a real-SRT regression test with a Linux CI job.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T19:16:39.188505Z 3828aa1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: c90e44aa49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

Pass 2 of 3 on head c90e44a. Focus areas: symlinks or races in the per-entry Git grants; entries created after registration; replay-probe grants; and whether any path lets a lane write checkout or sibling metadata on Linux or macOS.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c90e44aa49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/code/src/linked-worktrees.ts Outdated
metadata,
];
return { root, identity, checkoutRoot: checkout, commonGitDir, writableGitPaths };
const readableGitPaths = (await gitPathsBeside(commonGitDir, writableGitPaths)).sort();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh grants when read-only Git files are replaced

On Linux with the checkout beneath the read-denied worker home, regular files returned here become individual read-only bind mounts, but the command-root fingerprint records only their path names. Git commonly replaces files such as packed-refs and config by renaming a lockfile over the existing file; because that leaves readableGitPaths unchanged, subsequent requests reuse the sandbox and its bind to the old inode indefinitely. A lane or replay probe can therefore observe stale refs/config, and may lose refs after another process packs and removes the corresponding loose refs. Include identities for individually granted files in the fingerprint or otherwise recreate their grants after replacement.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 3828aa1: there are no individual file binds any more. The common directory is bound as a directory, so packed-refs, config and similar files replaced by rename are seen live.

Comment thread packages/code/src/native-sandbox.ts Outdated
Comment on lines +495 to +496
/** Never the common directory itself: its read bind would mask the writable binds beneath it. */
const laneGitPaths = [...readableGitPaths, ...writableGitPaths];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent split Git transactions through the tmpfs parent

When the checkout is beneath the read-denied worker home and packed-refs does not yet exist, omitting the common directory leaves the reconstructed .git parent as writable tmpfs while refs remains a writable host-backed bind. The lane Git guard permits git pack-refs, so Git can create packed-refs.lock and packed-refs successfully in tmpfs and then delete the real loose refs through the writable refs bind; when the sandbox exits, the packed file disappears and checkout or sibling branch refs are lost. Make the structural tmpfs parent non-writable or reject commands such as pack-refs whose transaction combines ungranted common-root files with writable descendants.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 3828aa1 by changing the mechanism. The lane is again granted read access to the whole common directory, so under the worker home .git is a real read-only host directory, not a tmpfs placeholder. On Linux, each existing writable Git directory is also listed as a deeper read-deny, which SRT processes after the home tmpfs and re-binds writable on top. pack-refs now fails to create packed-refs.lock (EROFS) before touching any loose ref. The live test runs git pack-refs --all in a lane and asserts every branch still resolves on the host.

…anting entries

Per-entry read grants left the common Git directory as a writable tmpfs
placeholder inside the worker home (pack-refs could strand refs there) and
pinned replaced files to stale inodes. Grant the whole directory read-only
again and, on Linux, list each existing writable Git directory as a deeper
read-deny so SRT re-binds its write mount after the ancestor read bind.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

Pass 3 of 4 on head 3828aa1. The mechanism changed since pass 2: whole-directory read grant plus Linux-only deeper read-denies for the writable Git directories. Please check the mount ordering this relies on in @anthropic-ai/sandbox-runtime 0.0.75, the probe path, and macOS behavior.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 3828aa1a5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

This is the final review for this PR (pass 4 of 4) of head 3828aa1. Please flag only blocking issues: correctness, security, or data loss.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 3828aa1a5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 3189cfd into main Sep 29, 2026
11 checks passed
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.

1 participant