Skip to content

local-mount: git-list population, warm reattach, and teardown safety#349

Merged
willwashburn merged 4 commits into
mainfrom
perf/local-mount-fast-launch
Jul 13, 2026
Merged

local-mount: git-list population, warm reattach, and teardown safety#349
willwashburn merged 4 commits into
mainfrom
perf/local-mount-fast-launch

Conversation

@willwashburn

@willwashburn willwashburn commented Jul 13, 2026

Copy link
Copy Markdown
Member

What this is

@relayfile/local-mount mounts are now fast enough to sit on an interactive launch path, and safe against external teardown.

Populationpopulation: 'git' | 'auto' enumerates the mount set via git ls-files --cached --others --exclude-standard instead of walking the directory tree. Gitignored trees (nested caches, stale worktrees, build outputs at any depth) never enter the mount. The repo's root .gitignore + .git/info/exclude rules join the mount's ignore set so reconcile/syncBack agree with the populated set; tracked-but-gitignored files are excepted and sync normally. Non-git projects, submodules, and pattern negations fall back to the walk. With includeGit, .git is one timestamp-preserving copy-on-write bulk clone.

Autosync priming — population records both sides' mtimes per copied file and seeds startAutoSync, which previously byte-compared every file pair in a full-tree priming walk. This also fixes a latent first-reconcile bug: project edits made between createMount and startAutoSync now win over the stale mount copy, and mount-side .git setup writes survive reconciles.

Quick comparison — copies preserve source mtimes everywhere, and content equality takes an rsync-style quick path (equal size + mtimes within 2ms), guarded by a 5s recency window so fresh writes always get byte-compared. syncBack skips no-sync-back subtrees (.git/**) instead of walking them.

Warm reuseattachMount() reattaches to a kept mount directory without wiping or re-copying; AutoSyncHandle.exportState() persists the per-file sync state so the next attach distinguishes deletions from creations (without it, files deleted from the project while a mount sat idle would be resurrected).

Teardown safety — autosync no longer mirrors an externally torn-down tree as a mass delete: if the mount root's marker vanishes while autosync is alive, mount-side "deletions" stop propagating to the project, and a vanished project root stops emptying the mount. Previously, removing a live session's mount directory caused autosync to delete every mounted file from the user's project — this is reachable in production today by any crash-cleanup or manual rm that races a live session.

Numbers (relay repo: 1,477 mount-relevant files inside a 75GB working directory)

Phase before after
Mount population 15.8s / 38,561 files ~1.5s / 1,477 files + .git clone
Autosync priming 9.3s ~0ms (seeded)
Session teardown (drain + syncBack) 10.8s <1s

Consumers

@agentworkforce/cli opts in with population: 'auto' and builds a warm-launch fast path on attachMount/exportState (companion PR in AgentWorkforce/workforce). Walk mode remains the default; existing callers see only the mtime-preservation and safety changes.

Semver: minor (new options/exports, no breaking API changes).

🤖 Generated with Claude Code

Review in cubic

…afety

Mount population can enumerate via git (population: 'git' | 'auto') so
gitignored trees never enter the mount, seeds autosync state during the
copy (priming pass eliminated), preserves mtimes for rsync-style quick
comparison, and bulk-clones .git copy-on-write. attachMount() +
AutoSyncHandle.exportState() support reusing a kept mount across
sessions with correct delete propagation. Autosync no longer mirrors an
externally torn-down mount root as a mass delete of the project.

On a 1.5k-file repo whose directory holds 38k+ non-ignored files, mount
setup drops from ~16s to ~1.5s and session teardown from ~11s to <1s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@willwashburn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a0cae987-44d2-4f2d-8112-062702830afd

📥 Commits

Reviewing files that changed from the base of the PR and between 295483e and f96da49.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • packages/local-mount/CHANGELOG.md
  • packages/local-mount/src/auto-sync.test.ts
  • packages/local-mount/src/auto-sync.ts
  • packages/local-mount/src/mount.ts
📝 Walkthrough

Walkthrough

Local mounts now support walk, git, and automatic population strategies, resumable autosync state, mtime-preserving copies, faster content comparisons, population-aware sync-back behavior, and safeguards against root teardown deletion cascades.

Changes

Local mount population and synchronization

Layer / File(s) Summary
Sync state and comparison lifecycle
packages/local-mount/src/auto-sync.ts, packages/local-mount/src/stat-compare.ts, packages/local-mount/src/auto-sync.test.ts
Autosync can seed and export per-file state, preserve mtimes, use stat-based equality checks, and avoid propagating deletions when either root disappears.
Population strategy selection and git population
packages/local-mount/src/mount.ts, packages/local-mount/src/mount.test.ts, packages/local-mount/src/index.ts, packages/local-mount/CHANGELOG.md
Mount creation supports walk, git, and auto strategies, git-aware ignores, optional .git cloning, strategy reporting, and the documented public exports and behavior.
Copy, traversal, and sync-back wiring
packages/local-mount/src/mount.ts
Walking, copying, directory listing, and sync-back use population-aware ignore predicates, seeded state, directory pruning, mtime preservation, and quick content comparisons.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Project
  participant createMount
  participant Git
  participant Mount
  participant AutoSync
  participant syncBack
  Project->>createMount: population options and ignore patterns
  createMount->>Git: resolve tracked files and ignore rules
  Git-->>createMount: eligible file list
  createMount->>Mount: populate selected files and optional .git
  createMount->>AutoSync: seed file sync state
  Project->>AutoSync: report edits or deletes
  AutoSync->>Mount: reconcile project changes
  Mount->>syncBack: provide mount changes
  syncBack->>Project: apply allowed changes
Loading

Poem

I’m a rabbit with a mount in bloom,
Git-picked files hop through the room.
State sleeps warm, mtimes shine,
Roots stay safe in sync’s design.
Thump, thump—reconcile just fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and captures the main changes: git-based population, warm reattach, and teardown safety.
Description check ✅ Passed The description clearly matches the changeset and summarizes the new population, reattach, and safety behavior.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/local-mount-fast-launch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new population option ('walk' | 'git' | 'auto') to optimize mount creation by leveraging git ls-files to exclude gitignored files. It also adds attachMount and exportState to support warm session reuse, preserves source mtimes to enable a fast-path content comparison, and implements safety guards against accidental mass deletions when roots vanish. The review feedback highlights two key issues: first, the check for patterns containing .git is overly broad and will trigger false positives on common patterns like .gitignore or .github; second, fs.cpSync does not support a mode option in Node.js, rendering the COPYFILE_FICLONE flag a silent no-op.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/mount.ts

@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: 295483e1b8

ℹ️ 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/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/mount.ts

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

Actionable comments posted: 4

🤖 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 `@packages/local-mount/src/auto-sync.ts`:
- Around line 160-162: Update the state snapshot creation around the auto-sync
initialization and the other applicable state-copy site so it clones each
FileState record rather than only creating a new Map. Ensure both supplied
initialState and exported snapshots contain detached FileState objects,
preserving the readonly/snapshot contract while keeping existing map contents
and reconciliation behavior unchanged.

In `@packages/local-mount/src/mount.ts`:
- Around line 199-217: Update the git population flow around cloneGitInto and
initialState so bulk-cloned .git/** entries are recorded in the autosync
FileState map before priming is skipped. Pass initialState into cloneGitInto if
supported, or seed equivalent .git/** state immediately afterward, while
preserving the existing file-count behavior.
- Around line 777-793: Update cloneGitInto so sourceStat.isFile() does not copy
a worktree .git pointer verbatim; resolve the referenced gitdir and rewrite the
pointer for the mounted repository, or reject this case by disabling includeGit.
Ensure the mount cannot retain a pointer to the original checkout’s gitdir while
preserving normal directory-based .git cloning.

In `@packages/local-mount/src/stat-compare.ts`:
- Around line 35-40: Update statsImplySameContent so matching size and mtime are
not treated as proof of equal content; use a cached checksum when available or
make this predicate only a preliminary hint that callers must confirm with byte
comparison before skipping reconciliation or sync-back. Remove the five-second
recency guard as the correctness mechanism, while preserving the existing
metadata checks as an optimization signal.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a749a94-5926-49b2-9970-7ea3a64e52e8

📥 Commits

Reviewing files that changed from the base of the PR and between dd55e1b and 295483e.

📒 Files selected for processing (7)
  • packages/local-mount/CHANGELOG.md
  • packages/local-mount/src/auto-sync.test.ts
  • packages/local-mount/src/auto-sync.ts
  • packages/local-mount/src/index.ts
  • packages/local-mount/src/mount.test.ts
  • packages/local-mount/src/mount.ts
  • packages/local-mount/src/stat-compare.ts

Comment thread packages/local-mount/src/auto-sync.ts Outdated
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/stat-compare.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/auto-sync.ts Outdated
Comment thread packages/local-mount/src/auto-sync.ts
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/mount.ts
Comment thread packages/local-mount/src/mount.ts Outdated
Comment thread packages/local-mount/src/mount.ts Outdated
willwashburn and others added 2 commits July 12, 2026 22:23
- Probe the real matchers for '.git' coverage instead of substring
  matching, so '.github'/'.gitignore' patterns no longer force the walk
  and glob spellings no longer bypass the guard
- Honor tracked nested .gitignore files (directory-scoped) and the
  user's global excludes in the sync predicate
- Seed autosync state for the bulk-cloned .git tree: project-side .git
  deletions propagate and pre-autosync mount .git edits survive
- Never copy a worktree-style .git pointer file into the mount: git
  inside the mount would mutate the host checkout's worktree metadata
- Skip state seeding for dereferenced symlink copies — auto-sync's
  symlink-rejecting stat would read the seeded entry as a project-side
  delete and remove the mount copy on first reconcile
- attachMount: run createMount's overlap validation on realpaths and
  throw for explicit population 'git' when preconditions fail
- Detach FileState snapshots on initialState import and exportState

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v0.10.22 release commit bumped the platform-package versions inside
package-lock.json without regenerating resolution entries, dropping the
linux-x64 entry entirely; every npm ci on a branch cut from that tip
fails with "Missing: @relayfile/mount-linux-x64@0.10.22 from lock file".
Entry restored to mirror its platform siblings, verified with
npm ci --dry-run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Relayfile Eval Review

Run: .relayfile/evals/runs/2026-07-13T02-43-35-388Z-HEAD-provider
Mode: provider
Git SHA: 2752837

Passed: 4 | Needs human: 0 | Reviewable: 0 | Missing output: 0 | Failed: 0 | Skipped: 0

Human Review Cases

No reviewable human-review cases captured Relayfile output.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/local-mount/src/mount.ts
…oject dir

The ls-files listing runs with `git -C projectDir`, so a (misconfigured)
relative excludes path resolves there — mirror that instead of letting
readIgnoreRuleLines fall back to process.cwd().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@willwashburn willwashburn merged commit 0b7e605 into main Jul 13, 2026
10 checks passed
@willwashburn willwashburn deleted the perf/local-mount-fast-launch branch July 13, 2026 10:49
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