Diagnosing Linux File Watchers Across WSL and Windows Filesystems
Trace inotify events from the actual WSL mount, separate missed events from quota failures, and build resilient watchers for mixed Windows and Linux edits.
Hot reload, IDE indexing, test runners, and build tools often depend on Linux inotify to learn that files changed. Under WSL, a project may live on the distro’s Linux filesystem or on a Windows drive mounted through DrvFs, and changes may originate from Linux programs, Windows editors, sync clients, or generated build output. A watcher that works in one location can fail in another for reasons that look identical at the application layer.
The reliable debugging method is to test the notification path directly, on the exact directory and from each actual writer. First distinguish an inotify resource limit from a filesystem event that never arrived; then check watch lifecycle, rename patterns, queue overflow, and the watcher framework’s own behavior. Do not treat polling, a larger sysctl, or a successful touch from Linux as proof that the Windows-to-WSL path is healthy.
What an inotify watcher actually guarantees
Linux inotify exposes a file descriptor containing a queue of structured events. A process creates an instance, adds watches to filesystem objects, and reads events from the descriptor. It is not a transaction log and does not promise one event per high-level application action. Event coalescing can occur, rename pairs can be separated or unmatched when an object leaves the watched tree, and queue overflow means events have been lost. Robust software must reconcile its model with the filesystem after loss or uncertainty.
Watches are attached to filesystem objects, not abstract project roots. Renaming or replacing a directory can invalidate a watch on the old object; a symlink points to an object whose target may not be inside the intended tree; and a recursive monitor normally has to add watches to directories as it discovers them. A framework may hide these details, but it cannot remove the underlying filesystem semantics.
WSL’s filesystem boundary matters. A path like $HOME/project is stored inside the distribution’s Linux filesystem; /mnt/c/project is a Windows filesystem exposed into Linux through DrvFs. Microsoft documented inotify support for Linux and Windows-side file changes on DrvFs, but the exact behavior still depends on the active WSL version, mount, writer, and operation. Verify the current path and tool combination rather than extrapolating from an old blog post or from a different WSL machine.
Confirm the path, mount, and platform before changing limits
Record the WSL servicing version, distro mode, Linux kernel, and path type. Run the commands in the distribution where the watcher fails:
wsl.exe --version
wsl.exe --list --verbose
uname -a
findmnt -T "$HOME/project"
findmnt -T /mnt/c/project
findmnt reports the mount that contains each path. Compare a failing path with a control directory on the Linux-native filesystem. Keep the test tree small and make sure the watcher actually watches the exact directory where the edit lands. A Windows drive letter mounted at /mnt/c is not the same filesystem implementation as /home/user/project, even though both appear as ordinary directories to many commands.
If a project is on a Windows mount because Windows applications must edit it, keep that requirement explicit. If only a Linux editor and Linux tools need the tree, place it in the distribution’s Linux filesystem and access it from Windows through the supported WSL file-sharing path or a WSL-aware development tool. This is a workload choice, not a universal rule that every project must be moved.
Prove event delivery with a minimal test
Install a notification utility in the distro if it is not already present, then watch the exact directory. inotify-tools packages the inotifywait command on Ubuntu and Debian:
sudo apt update
sudo apt install inotify-tools
inotifywait --monitor --recursive \
--event create,modify,close_write,moved_from,moved_to,delete \
--format '%w%f %e' "$HOME/project"
In a second Linux shell, create, write, rename, and remove a temporary file. Then repeat with the Windows editor or Windows-side command that normally changes the project. Run the same test under /mnt/c/project. The test should not target a real source file or a directory that the application is actively modifying.
The result narrows the fault:
| Direct test | Likely boundary to investigate next |
|---|---|
| Linux edits and Windows edits both appear on the Linux filesystem | The filesystem event path is working there; inspect the framework’s filters, ignored paths, debounce behavior, or process environment. |
Linux edits appear on /mnt/c, but Windows edits do not |
Investigate the current DrvFs/WSL path and the Windows program’s save strategy. Atomic replace, sync software, or a different target directory can behave differently from an in-place write. |
| Direct events appear but a development server does not reload | Check the framework’s configured watch root, exclude globs, symlink handling, container boundary, and whether its process inherited the expected environment. |
| No events appear on either filesystem | Confirm that inotifywait is watching the path you modify, that the directory exists, and that the watcher reports no permission or resource error. |
Some editors save by writing a temporary file and renaming it over the original. A watcher that subscribes only to modify on one file can miss the effective replacement. Watch the parent directory and include create, close-write, move, and delete events when diagnosing. This also explains why a test that merely appends to a file may pass while the editor’s save action does not.
Separate event loss from exhausted watch resources
Tools that watch large dependency trees can create many inotify watches. Linux exposes counters such as max_user_watches, max_user_instances, and max_queued_events under /proc/sys/fs/inotify; the Linux manual documents what each limit controls. Check which files exist and their values before drawing conclusions:
for setting in max_user_watches max_user_instances max_queued_events; do
path="/proc/sys/fs/inotify/$setting"
if [ -r "$path" ]; then
printf '%s=' "$setting"
cat "$path"
else
printf '%s is not readable or not exposed\n' "$setting"
fi
done
An ENOSPC returned while adding a watch can indicate an exhausted watch limit; it does not necessarily mean the host disk is full. An inotify queue can also overflow under a burst of changes. Linux reports IN_Q_OVERFLOW when that happens, and queued events may be lost. If a library logs a watch-limit or queue error, capture it alongside the settings and exact path. A watcher that silently misses an event should be tested against a simpler direct monitor before changing global kernel parameters.
Do not copy a native-Linux sysctl recipe blindly into WSL. WSL has had version-specific behavior for exposed inotify sysctl nodes, and setting a value in /etc/sysctl.conf does not prove that the running WSL kernel accepted or applied it. Confirm the node exists, the write succeeded, the value read back changed, and a measured watch-creation test improved before treating a limit increase as a fix. Increasing a limit consumes kernel resources and can conceal a tool watching far more of node_modules, generated output, or cache directories than intended.
Prefer reducing unnecessary watch scope first: configure the application to ignore build output, caches, dependency directories, and unrelated monorepo roots; close duplicate editor or test processes; and restart stale watcher processes so released watches are actually freed. The Linux manual notes that closing the last descriptor for an inotify instance frees its associated watches.
Make the application recover from event streams that are not perfect
An event means “reconcile this path,” not “the entire build graph is now correct.” Coalesce bursts, debounce expensive rebuilds, and re-stat files before acting. Treat rename pairs as potentially racy; a move out of a watched tree may produce no matching IN_MOVED_TO event there. When the process sees queue overflow, a watch removal, or a path replacement, invalidate the affected cache and rescan the directory tree.
Watch directories for editor-style atomic saves instead of depending exclusively on a file inode. When a build output directory is replaced, re-register watches on its new directories. Avoid watching an entire mounted drive for convenience: it multiplies event load, increases watch consumption, and makes irrelevant Windows activity look like application changes. If polling is the fallback for a required cross-boundary path, scope the poll to the smallest tree and measure its CPU and latency cost.
In containers running inside WSL, test from the same namespace and mount view as the process. A bind mount, Docker Desktop integration, or a remote editor may place the application on a path whose event behavior differs from the path visible in an interactive shell. Compare pwd, findmnt, and the process’s effective watch root inside that exact environment.
Acceptance criteria for a development watcher
Use a repeatable test matrix instead of “it reloaded once.” For each target path, exercise a new file, in-place modification, editor-style replace/rename, deletion, and a burst of changes. Repeat each action from Linux and, where required, from the Windows application that owns the edit. Record whether the direct inotifywait monitor receives an event and whether the actual development tool rebuilds or reloads.
The result should be deterministic enough for the workflow: the intended source file changes are detected within the team’s acceptable latency, temporary and ignored paths do not trigger unnecessary rebuilds, queue or watch errors are visible, and a rescan restores consistency after a detected overflow or restart. If Linux-native storage passes and DrvFs does not for an essential Windows editor workflow, choose explicitly between moving the project, using a WSL-aware editor, or using a carefully scoped polling mode.
This boundary-first method avoids the most common dead end: turning a missing event into a generic “increase inotify watches” change. Check the exact mount, event source, operation type, and resource error first; then change only the layer that the evidence implicates.
Related:
- How WSL Bridges Two Completely Different Filesystems
- Fixing Slow File I/O When Working on /mnt/c from WSL2
Sources: