fix: dynamic root ModTime for NFS cache invalidation#67
Merged
jamestexas merged 5 commits intomainfrom Mar 5, 2026
Merged
Conversation
GraphFS.Lstat("/") returned a fixed mountTime, causing macOS NFS clients
to cache the initial (possibly empty) directory listing even with noac.
After HotSwapGraph.Swap, the client saw the same mtime and served the
stale cached listing.
Now Lstat("/") queries the graph's root node for ModTime. CompositeGraph
returns time.Now(), so NFS clients see a changed mtime and re-read the
directory. MemoryStore returns zero ModTime, falling back to mountTime.
Reproduces the x-ray NFS scenario:
- HotSwapGraph starts with empty MemoryStore
- Swap to CompositeGraph with mounted sub-graphs
- Verify ReadDir("/") returns composite mount names after Swap
- Verify root Lstat returns dynamic mtime from CompositeGraph
- Verify sub-graph content is readable through the full chain
staticFileInfo.Sys() returned *syscall.Stat_t{Ino: 0} for every file,
causing ALL NFS entries to share Fileid=0. macOS NFS client uses Fileid
as inode numbers β duplicate values break directory listings entirely.
Now Sys() returns nil, making go-nfs fall back to FNV hash of the full
path for unique Fileid values. Tradeoff: uid/gid default to 0 (root
ownership), but directory listings actually work.
All staticFileInfo construction now goes through newFileInfo(fullPath, ...) which auto-computes ino from FNV hash. This: - Fixes Fileid=0 bug that broke macOS NFS directory listings - Preserves uid/gid in Sys() (reverts nil-Sys approach) - Makes it impossible to forget ino on new virtual file sites Adds TestUniqueFileids to catch regressions.
A mounted sub-graph that delegates back to the CompositeGraph (e.g. x-ray's focus.Router) caused infinite recursion β stack overflow. Add atomic depth counter (maxCallerDepth=2) to short-circuit the cycle. Test confirms: without guard β stack overflow, with guard β returns cleanly.
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.
Summary
GraphFS.Lstat("/")returned a fixedmountTime, causing macOS NFS clients to cache the initial directory listing even withnoacHotSwapGraph.Swap(), client saw same mtime β served stale cached (empty) listingModTimeβCompositeGraphreturnstime.Now(), forcing NFS client cache invalidationContext
x-ray mounts an NFS filesystem backed by a
HotSwapGraphthat starts empty and gets swapped to aCompositeGraphwhen a browser tab connects. The macOS NFS client cached the initial empty listing and never refreshed.Test plan
go test ./internal/nfsmount/passesXRAY_NFS_MOUNT=trueagentd βls /tmp/xray-mache/showsbrowser/,iterm/, etc. after tab connectsπ€ Generated with Claude Code