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.
- 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(bothfix/recv-eof-on-unmountandfix/recv-eof-and-attrcachebranches tested) - Fresh
git cloneof tup
./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.
Running tup --debug-fuse and capturing the full FUSE operation trace reveals the following sequence:
- Tup runs the lua binary from
src/luabuiltin/via the FUSE filesystem - The macOS NFS client resolves
../lua/lua, opens and reads the binary - After loading the binary, the NFS client does a speculative
OPENDIR+READDIRonsrc/lua/(the directory containing the binary) - It then does individual
LOOKUPon every file returned by readdir — including.gitignore - Each
LOOKUPtriggerstup_fs_getattr()which callstup_fuse_handle_file(path, stripped, ACCESS_READ)(fuse_fs.cline 436) - 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).
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.
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 |
-
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
arm64fromCMAKE_OSX_ARCHITECTURES. -
Did the
Tuprules.tup/Tupfilechanges 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)? -
Could tup filter out
.gitignorefrom dependency tracking in the FUSE layer? Since tup generates these files itself,tup_fuse_handle_file()intup_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. -
Could
getattrbe excluded from dependency tracking? The speculative LOOKUPs are just the NFS client doingstat()— not actual file reads. If onlyopen()/read()were tracked as dependencies (notgetattr), this class of problem would go away entirely. However, this might break other dependency detection that relies onstat()being tracked.
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.