Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#774·fsnotify

inotify: watch is dropped on unmount without any event or error

Author: twz123Created Sep 4, 2026Updated Sep 4, 2026
Labelsbug

Describe the bug

When the file system on which a watched path lives is unmounted, the inotify backend removes the watch from its internal state, but does not send anything to Events or Errors. After unmounting, the watcher is effectively useless. The channels are open, but nothing arrives or is logged. A consumer sees an "idle directory". The watched path might still exist on the underlying file system that became visible again after the other one being unmounted. However, it's now a completely different one. Changes to that uncovered path are not picked up by inotify anymore. Currently, the only way to detect this is to poll WatchList() and realize that the path being watched is no longer listed.

So this is why this a problem IMO: The result is that the state of the file system has completely diverged from what an fsnotify consumer thinks it is. This might be mission critical to some applications, and they might very well do silly things because of that.

I see three ways how to tackle this:

  1. Send a synthetic Remove for the watched path. Matches what the directory deletion produces. Consumers that survive a delete handle it already. Downside: It's not a Remove of course. But application-wise, it would probably be a defensible trade-off?
  2. Send an error on Errors, with a new exported sentinel, so folks can distinguish it from other watch errors. This is maybe a better option, but it very much depends on how applications handle things that come in via the error channel. Maybe just logging, maybe killing the watch...
  3. Widen the event model as in #519, e.g. an Unmount op. Most precise, but opt-in by design, so consumers who don't ask for it would still see nothing unless explicitly requested. This option could be combined with 1 or 2 later on. If 1 or 2 are implemented, and the Unmount op actually lands, fsnotify could change the behavior from 1 or 2 to 3, based on the fact if unmount events have been requested or not.

Code to Reproduce

go.mod
module repro

go 1.26

require (
        github.com/fsnotify/fsnotify v1.10.2-0.20260511064106-20b1e15ef3c7
        golang.org/x/sys v0.35.0
)
main.go
go
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/fsnotify/fsnotify"
	"golang.org/x/sys/unix"
)

func main() {
	base, _ := os.MkdirTemp("", "fsnotify-unmount-")
	defer os.RemoveAll(base)
	mnt := filepath.Join(base, "mnt")
	os.Mkdir(mnt, 0o755)
	if err := unix.Mount("none", mnt, "tmpfs", 0, ""); err != nil {
		fmt.Println("mount:", err, "(run as root or under `unshare -rm`)")
		os.Exit(2)
	}

	w, _ := fsnotify.NewWatcher()
	defer w.Close()
	if err := w.Add(mnt); err != nil {
		panic(err)
	}
	fmt.Printf("WatchList before unmount: %q\n", w.WatchList())

	os.WriteFile(filepath.Join(mnt, "f"), []byte("x"), 0o644)
	drain(w, "sanity", 300*time.Millisecond)

	if err := unix.Unmount(mnt, 0); err != nil {
		panic(err)
	}
	fmt.Println("unmounted", mnt)
	drain(w, "after-unmount", 2*time.Second)
	fmt.Printf("WatchList after unmount: %q\n", w.WatchList())

	// The path still exists (the mount point is a plain directory again) and
	// is writable, but the watcher never reports anything for it again.
	os.WriteFile(filepath.Join(mnt, "g"), []byte("y"), 0o644)
	drain(w, "write-after-unmount", 500*time.Millisecond)

	// The watcher itself is fine: re-adding the path makes it work again. But
	// nothing told us that we had to.
	if err := w.Add(mnt); err != nil {
		panic(err)
	}
	os.WriteFile(filepath.Join(mnt, "h"), []byte("z"), 0o644)
	drain(w, "write-after-re-add", 500*time.Millisecond)
}

func drain(w *fsnotify.Watcher, label string, d time.Duration) {
	deadline := time.After(d)
	for {
		select {
		case ev := <-w.Events:
			fmt.Printf("  [%s] Events: %v\n", label, ev)
		case err := <-w.Errors:
			fmt.Printf("  [%s] Errors: %v\n", label, err)
		case <-deadline:
			fmt.Printf("  [%s] nothing received in %v\n", label, d)
			return
		}
	}
}
bash
$ go mod tidy && go build && FSNOTIFY_DEBUG=1 unshare -rm ./repro
FSNOTIFY_DEBUG: 18:07:08.922327912  AddWith("/tmp/fsnotify-unmount-3954703671/mnt")
WatchList before unmount: ["/tmp/fsnotify-unmount-3954703671/mnt"]
FSNOTIFY_DEBUG: 18:07:08.922446081  IN_CREATE                      → "/tmp/fsnotify-unmount-3954703671/mnt/f"
FSNOTIFY_DEBUG: 18:07:08.922482041  IN_MODIFY                      → "/tmp/fsnotify-unmount-3954703671/mnt/f"
  [sanity] Events: CREATE        "/tmp/fsnotify-unmount-3954703671/mnt/f"
  [sanity] Events: WRITE         "/tmp/fsnotify-unmount-3954703671/mnt/f"
  [sanity] nothing received in 300ms
FSNOTIFY_DEBUG: 18:07:09.223129061  IN_ISDIR|IN_UNMOUNT            → "/tmp/fsnotify-unmount-3954703671/mnt"
unmounted /tmp/fsnotify-unmount-3954703671/mnt
  [after-unmount] nothing received in 2s
WatchList after unmount: []
  [write-after-unmount] nothing received in 500ms
FSNOTIFY_DEBUG: 18:07:11.724675713  AddWith("/tmp/fsnotify-unmount-3954703671/mnt")
FSNOTIFY_DEBUG: 18:07:11.724754952  IN_CREATE                      → "/tmp/fsnotify-unmount-3954703671/mnt/h"
FSNOTIFY_DEBUG: 18:07:11.724785882  IN_MODIFY                      → "/tmp/fsnotify-unmount-3954703671/mnt/h"
  [write-after-re-add] Events: CREATE        "/tmp/fsnotify-unmount-3954703671/mnt/h"
  [write-after-re-add] Events: WRITE         "/tmp/fsnotify-unmount-3954703671/mnt/h"
  [write-after-re-add] nothing received in 500ms

File operations to reproduce

The repro code handles this.

Which operating system and version are you using?

bash
$ uname -srvmo
Linux 7.1.9 #1-NixOS SMP PREEMPT_DYNAMIC Wed Aug 19 16:20:32 UTC 2026 x86_64 GNU/Linux

Which fsnotify version are you using?

v1.10.2-0.20260511064106-20b1e15ef3c7

Did you try the latest main branch?

Yes

Source: fsnotify/fsnotify

View original on GitHubView discussion on GitHub