Skip to content

Instantly share code, notes, and snippets.

@petemoore
Created March 24, 2026 21:56
Show Gist options
  • Select an option

  • Save petemoore/3f02e3913f067f4d4c7f9da5ace17f17 to your computer and use it in GitHub Desktop.

Select an option

Save petemoore/3f02e3913f067f4d4c7f9da5ace17f17 to your computer and use it in GitHub Desktop.
FUSE-T + tup: .gitignore dependency tracking failure on macOS — analysis by Claude (AI assistant) for Pete Moore

FUSE-T + tup: .gitignore dependency tracking failure on macOS

This is an analysis of a tup bootstrap failure when using FUSE-T on macOS bare metal hardware. I'm Claude (Anthropic's AI assistant) — Pete asked me to help diagnose and document this issue.

Environment

  • macOS 15.7.4 (build 24G517), Apple Silicon (M4 Pro, Mac16,7)
  • FUSE-T 1.0.54 (brew install fuse-t)
  • Patched libfuse from petemoore/libfuse (both fix/recv-eof-on-unmount and fix/recv-eof-and-attrcache branches tested)
  • Fresh git clone of tup

The error

./bootstrap.sh fails 100% of the time during tup's self-rebuild phase:

* ~52% 43) src/luabuiltin: ../lua/lua xxd.lua builtin.lua luabuiltin.h
 *** tup messages ***
tup error: Missing input dependency - a file was read from, and was not
specified as an input link for the command.
 - [1725] src/lua/.gitignore
 *** Command ran successfully, but failed due to errors processing input
dependencies.

The luabuiltin command (../lua/lua xxd.lua builtin.lua luabuiltin.h) is flagged for reading src/lua/.gitignore — a tup-generated file — without declaring it as an input.

Root cause

Running tup --debug-fuse and capturing the full FUSE operation trace reveals the following sequence:

  1. Tup runs the lua binary from src/luabuiltin/ via the FUSE filesystem
  2. The macOS NFS client resolves ../lua/lua, opens and reads the binary
  3. After loading the binary, the NFS client does a speculative OPENDIR + READDIR on src/lua/ (the directory containing the binary)
  4. It then does individual LOOKUP on every file returned by readdir — including .gitignore
  5. Each LOOKUP triggers tup_fs_getattr() which calls tup_fuse_handle_file(path, stripped, ACCESS_READ) (fuse_fs.c line 436)
  6. Tup records this as the luabuiltin command reading a generated file it didn't declare as input → error

The key underlying issue is that FUSE-T's NFS backend means all FUSE operations arrive with pid: 0 (the NFS client doesn't convey the original process PID). So context_check() in fuse_fs.c can't distinguish between the lua process's real file accesses and the NFS client's speculative directory scanning — getpgid(0) returns tup's own pgid, which always matches.

With macFUSE, the kernel module passes real PIDs, so these speculative accesses would be filtered out (or they don't happen at all since there's no NFS layer involved).

CI passes — but why?

The GitHub Actions CI (macos-latest, also macOS 15.7.4 arm64, same FUSE-T 1.0.54) passes every time. We pushed a debug branch that captures the FUSE trace in CI and compared:

Pete's Mac (FAILS) CI (PASSES)
.gitignore LOOKUPs Yes, every run Never
All PIDs 0 0
OPENDIR on src/lua/ Yes (1 op) No
Total READDIR ops 2 14
Total GETATTR ops 18,539 27,688
Total LOOKUP ops 7,780 12,789
hw.model Mac16,7 (bare metal M4 Pro) VirtualMac2,1 (VM, 3 CPUs)

CI has more total FUSE operations, but its NFS client never does the speculative READDIR + LOOKUP sequence on src/lua/. The behavior is deterministic — 0% pass rate locally (tested 15+ times across multiple configurations), 100% in CI.

What we ruled out

Each of these was tested independently, with multiple attempts per configuration:

Hypothesis What we tried Result
libfuse branch Tested both fix/recv-eof-on-unmount (website) and fix/recv-eof-and-attrcache (CI) Both fail
go-nfsv4 version Extracted 1.0.49 binary (what CI has) and swapped it in Still fails
NFS is_mobile sysctl CI has vfs.generic.nfs.client.is_mobile: 0, Pete has 1. Set to 0 Still fails
Pre-existing .gitignore Tested on completely fresh git clone Still fails
Spotlight indexing Was disabled, re-enabled it Still fails
NFS attribute cache fix/recv-eof-and-attrcache branch always passes --attrcache=false Still fails
Architecture / macOS version Both arm64, both macOS 15.7.4 build 24G517 Identical

Open questions

  1. What hardware are you (Mike) on? Intel or Apple Silicon? If Intel, the NFS client kernel code may behave differently — and that would also explain why you needed to remove arm64 from CMAKE_OSX_ARCHITECTURES.

  2. Did the Tuprules.tup / Tupfile changes affect anything beyond the library search path? You mentioned changing pkg-config fuse stuff to point at a local build instead of /usr/local. Could that change the FUSE mount behavior somehow (e.g. a different library being loaded at runtime)?

  3. Could tup filter out .gitignore from dependency tracking in the FUSE layer? Since tup generates these files itself, tup_fuse_handle_file() in tup_fs_getattr() could skip them. That would fix this specific symptom, though the broader issue of NFS client speculative accesses being incorrectly tracked as dependencies would remain.

  4. Could getattr be excluded from dependency tracking? The speculative LOOKUPs are just the NFS client doing stat() — not actual file reads. If only open()/read() were tracked as dependencies (not getattr), this class of problem would go away entirely. However, this might break other dependency detection that relies on stat() being tracked.

Recommendation

For now, the CI is valuable regardless — it proves tup compiles and tests pass on macOS with FUSE-T. But the website installation instructions may need a caveat that FUSE-T doesn't work reliably on all machines, or macFUSE should remain the primary recommendation with FUSE-T noted as experimental.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment