bug#81531: 30.2; filenotify: moving file out of watched directory delays the "deleted" event until file is later moved back in

Michael Albinus via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <[email protected]> Wed, 05 Aug 2026 18:21:47 +0200
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
Michael Albinus <[email protected]> writes:

Hi,

> This is the problematic action. We get from inotify only a 'moved-from
> "bar"' event. The corresponding 'moved-to' event is not returned,
> because the file path is ouside of the watched directory.
>
> filenotify.el is instructed, to wait for the next backend event after
> 'moved-from'. This is needed to decide what to do. Since no second
> backend event arrives, nothing is sent to the handler. This is what you
> have observed.

FTR, if you have two independent file watchers over directories, and you
move a file between these two directories, filenotify.el detects this as
rename, and reports it twice in both callbacks. See this scenario:

# mkdir foo
# mkdir bar
# ~/src/emacs/src/emacs -l filenotify --eval "(setq file-notify-debug t)" --eval "(file-notify-add-watch \"foo\" '(change) #'ignore)" --eval "(file-notify-add-watch \"bar\" '(change) #'ignore)" &
# touch foo/baz

--8<---------------cut here---------------start------------->8---
file-notify-handle-event (file-notify ((1 . 0) (create) "baz" 0) file-notify--callback-inotify)
file-notify--call-handler (1 . 0) created "/home/albinus/.emacs.d/foo/baz" nil #s(file-notify--watch "/home/albinus/.emacs.d/foo" nil ignore) "/home/albinus/.emacs.d/foo" "/home/albinus/.emacs.d/foo"
--8<---------------cut here---------------end--------------->8---

# mv foo/baz bar/

--8<---------------cut here---------------start------------->8---
file-notify-handle-event (file-notify ((1 . 0) (moved-from) "baz" 1134534) file-notify--callback-inotify)
file-notify-handle-event (file-notify ((2 . 0) (moved-to) "baz" 1134534) file-notify--callback-inotify)
file-notify--call-handler (1 . 0) renamed "/home/albinus/.emacs.d/foo/baz" "/home/albinus/.emacs.d/bar/baz" #s(file-notify--watch "/home/albinus/.emacs.d/foo" nil ignore) "/home/albinus/.emacs.d/foo" "/home/albinus/.emacs.d/foo"
file-notify--call-handler (2 . 0) renamed "/home/albinus/.emacs.d/foo/baz" "/home/albinus/.emacs.d/bar/baz" #s(file-notify--watch "/home/albinus/.emacs.d/bar" nil ignore) "/home/albinus/.emacs.d/bar" "/home/albinus/.emacs.d/bar"
--8<---------------cut here---------------end--------------->8---

# rm -rf foo bar

--8<---------------cut here---------------start------------->8---
file-notify-handle-event (file-notify ((1 . 0) (delete-self) "/home/albinus/.emacs.d/foo" 0) file-notify--callback-inotify)
file-notify--call-handler (1 . 0) deleted "/home/albinus/.emacs.d/foo" nil #s(file-notify--watch "/home/albinus/.emacs.d/foo" nil ignore) "/home/albinus/.emacs.d/foo" "/home/albinus/.emacs.d/foo"
file-notify-handle-event (file-notify ((1 . 0) (ignored) "/home/albinus/.emacs.d/foo" 0) file-notify--callback-inotify)
file-notify-handle-event (file-notify ((1 . 0) stopped "/home/albinus/.emacs.d/foo") ignore)
file-notify-handle-event (file-notify ((2 . 0) (delete) "baz" 0) file-notify--callback-inotify)
file-notify--call-handler (2 . 0) deleted "/home/albinus/.emacs.d/bar/baz" nil #s(file-notify--watch "/home/albinus/.emacs.d/bar" nil ignore) "/home/albinus/.emacs.d/bar" "/home/albinus/.emacs.d/bar"
file-notify-handle-event (file-notify ((2 . 0) (delete-self) "/home/albinus/.emacs.d/bar" 0) file-notify--callback-inotify)
file-notify--call-handler (2 . 0) deleted "/home/albinus/.emacs.d/bar" nil #s(file-notify--watch "/home/albinus/.emacs.d/bar" nil ignore) "/home/albinus/.emacs.d/bar" "/home/albinus/.emacs.d/bar"
file-notify-handle-event (file-notify ((2 . 0) (ignored) "/home/albinus/.emacs.d/bar" 0) file-notify--callback-inotify)
file-notify-handle-event (file-notify ((2 . 0) stopped "/home/albinus/.emacs.d/bar") ignore)
--8<---------------cut here---------------end--------------->8---

See the different watch descriptors (1 . 0)  and (2 . 0)

This works only for the inotify backend.

Best regards, Michael.