mountx logomountx

Troubleshooting

The things that will bite you, and how to get out of each.

#Don't use your own mount from the serving process

This is the one that catches everyone.

A synchronous fs call against your own mountpoint, from the process serving it, deadlocks outright: the call blocks the event loop, and the request it produced can only be answered by that same event loop.

Enough concurrent async calls wedge it too, and less obviously. Every /dev/fuse read parks a libuv threadpool thread, and so does every fs call your driver makes. fs.rm(dir, { recursive: true }) over a few hundred entries will exhaust the pool that the mount's read loop also needs, and the process stops.

The fix: put the client in another process — a shell, a test child process, anything. If you truly must drive it in-process, keep the concurrency well below the pool size and raise UV_THREADPOOL_SIZE. The full explanation is at the top of src/fuse/mount.ts.

#Don't call process.exit() while mounted

Node's exit path waits for the threadpool, and a live mount always has reads parked there. The process hangs.

// wrong
process.exit(1);

// right
await mounted.unmount();
process.exitCode = 1;

This is also why mountx's built-in signal handlers unmount and then re-raise the signal instead of exiting directly.

#A stale mountpoint after a crash

If the serving process dies without unmounting, the folder is left stale rather than frozen — ls answers ENOTCONN instead of hanging. Clean it up:

# FUSE, mounted unprivileged
fusermount3 -u /mnt/point

# FUSE, mounted as root
sudo umount -l /mnt/point

# NFS
sudo umount -f /mnt/point   # macOS: see the consent note below

#unmount() threw

It throws for two reasons, and both are recoverable — unmount() is retryable after a failure.

  • umount(8) refused — the mountpoint is busy. Something has a working directory or an open file inside it. lsof +D /mnt/point (or fuser -m /mnt/point) finds it.
  • The deadline passedunmountTimeout (default 10 s) expired and the connection had to be forced down. Usually a driver that stopped answering. If yours can legitimately take longer to quiesce, raise the timeout.

#unmount() hangs on macOS

macOS puts network volumes behind a sandbox approval that is never prompted for a command-line process. Without it, umount blocks and umount -f answers EPERM.

mountx names this case specifically rather than blaming your driver, and tells you the mount survived. The fix is a grant made in advance: Full Disk Access for whatever app the process is attributed to — the terminal, your IDE, the CI agent. See NFSv3 § the macOS consent gate.

#"no transport can mount on this host"

mount() from mountx/auto throws this when the probe finds nothing usable, and the message names what each transport is missing. Ask directly for the detail:

import { probeTransports } from "mountx/auto";

const probe = await probeTransports();
console.log(probe.fuse.reason, probe.nfs.reason);

Common causes:

reasonfix
no /dev/fusethe fuse module is not loaded, or a container was not given the device (docker run --device /dev/fuse)
no fusermount3apt install fuse3 / dnf install fuse3, or run as root
no prebuilt addon for this platformunprivileged FUSE mounting is unavailable here; root still works, and NFS is unaffected
no /sbin/mount.nfsapt install nfs-common / dnf install nfs-utils
"mounting NFS needs root on Linux"sudo, or use FUSE
Windowsneither transport mounts there. createNfsServer() still runs, and you can mount it from elsewhere

#allowOther was refused

An unprivileged FUSE mount additionally needs user_allow_other in /etc/fuse.conf. Without it fusermount3 refuses the mount and says so. Add the line, or mount as root.

#EPERM mounting NFS on macOS

macOS needs no root for NFS, but an ordinary user may only mount onto a directory that user owns. mountx checks this at mount time — it is a fact about the path, not about the host — and refuses with a message naming the owner. chown the mountpoint, or pick one you own.

#ESTALE on a file that is still open

You are on NFSv3, and the file was deleted while open. NFSv3 is stateless — there is no open/release, so a handle to a deleted file has nothing to refer to. FUSE keeps it readable; NFS cannot.

This is the single behavioural difference between the transports, and it is why mountx/auto prefers FUSE wherever FUSE works.

#Something the driver returned is being ignored

Check the resolved capabilities:

createLoopback(driver).capabilities;

handles and atomicRename cannot be inferred from a driver's shape and default to false — if you implemented them, declare them. Everything else follows from which methods exist.

#Stale kernel cache

Values are correct in your driver and stale through the mountpoint? That is the attribute/entry cache doing its job. Either lower the timeouts, or — better — invalidate precisely:

if (mounted.transport === "fuse") mounted.notifyInvalInode(ino);

#Recovering a wedged dev host

If a mount has genuinely locked up and nothing will detach it, .agents/environment.md has the verified recovery procedures — including the root-only /sys/fs/fuse/connections/<n>/abort, which aborts the connection and lets the parked requests fail.

mountx logo

mountx  Write a filesystem in JavaScript, mount it as a real folder.