Skip to content

Fix DirDeletedEvent not being raised for deleted directories on Windows#1156

Draft
wingding12 wants to merge 1 commit into
gorakhargosh:masterfrom
wingding12:fix-dir-deleted-event-windows
Draft

Fix DirDeletedEvent not being raised for deleted directories on Windows#1156
wingding12 wants to merge 1 commit into
gorakhargosh:masterfrom
wingding12:fix-dir-deleted-event-windows

Conversation

@wingding12

Copy link
Copy Markdown

Summary

On Windows, when a directory was deleted, watchdog incorrectly raised FileDeletedEvent instead of DirDeletedEvent. This happened because deleted items cannot be queried with os.path.isdir() - they no longer exist.

Solution

Use ReadDirectoryChangesExW (available on Windows 10+) instead of ReadDirectoryChangesW. The extended API returns FILE_NOTIFY_EXTENDED_INFORMATION which includes FileAttributes, allowing us to correctly identify whether a deleted item was a directory.

Changes

src/watchdog/observers/winapi.py:

  • Add ReadDirectoryChangesExW function definition
  • Add FileNotifyExtendedInformation structure matching the Windows API
  • Add is_directory field to WinAPINativeEvent dataclass
  • Update _parse_event_buffer to extract FileAttributes and determine if the item was a directory
  • Update DirectoryChangeReader._run_inner to use ReadDirectoryChangesExW

src/watchdog/observers/read_directory_changes.py:

  • Update WindowsApiEmitter.queue_events to use is_directory for deleted events instead of always using FileDeletedEvent

tests/test_observers_winapi.py:

  • Add test_directory_deleted_event to verify directories produce DirDeletedEvent
  • Add test_file_deleted_event to verify files still produce FileDeletedEvent

Notes

  • ReadDirectoryChangesExW is only available on Windows 10 and later
  • The _parse_event_buffer function now accepts an extended parameter to support both old and new formats for backward compatibility

Related Issues

Fixes #1153
Related to #1154

Test Plan

  • Run tests on Windows 10+ to verify DirDeletedEvent is raised for deleted directories
  • Run tests to verify FileDeletedEvent is still raised for deleted files
  • Verify no regressions in other event types

On Windows, when a directory was deleted, watchdog incorrectly raised
FileDeletedEvent instead of DirDeletedEvent. This happened because
deleted items cannot be queried with os.path.isdir() - they no longer
exist.

The fix uses ReadDirectoryChangesExW (available on Windows 10+) instead
of ReadDirectoryChangesW. The extended API returns FILE_NOTIFY_EXTENDED_INFORMATION
which includes FileAttributes, allowing us to correctly identify whether
a deleted item was a directory.

Changes:
- Add ReadDirectoryChangesExW function and FileNotifyExtendedInformation
  structure to winapi.py
- Add is_directory field to WinAPINativeEvent dataclass
- Update _parse_event_buffer to extract FileAttributes and determine
  if the item was a directory
- Update WindowsApiEmitter.queue_events to use is_directory for deleted
  events instead of always using FileDeletedEvent
- Add tests for directory and file deletion events

Fixes gorakhargosh#1153
Related to gorakhargosh#1154
@BoboTiG

BoboTiG commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator

I was not aware of this API, that's fantastic!

It means will drop support for Windows <10. Please add a line in the changelog for the change + Windows support change.

self.queue_event(sub_created_event)
elif winapi_event.is_removed:
self.queue_event(FileDeletedEvent(src_path))
# Use is_directory from extended file info to correctly identify

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the comment.

LPVOID, # FileIOCompletionRoutine # lpCompletionRoutine
)

# ReadDirectoryChangesExW is available on Windows 10 and later.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you can remove all stuff related to ReadDirectoryChangesW.



def _parse_event_buffer(read_buffer: bytes) -> list[tuple[int, str]]:
def _parse_event_buffer(read_buffer: bytes, *, extended: bool = False) -> list[tuple[int, str, bool]]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change the signature. Use extended attrs by default.

class WinAPINativeEvent:
action: int
src_path: str
is_directory: bool = False # Whether the item was a directory (from extended info)

@BoboTiG BoboTiG Jan 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the comment since the name is pretty explicit.

buf = self._buf_queue.get(timeout=timeout)
except queue.Empty:
break
events.extend(_parse_event_buffer(buf))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the signature won't change, those edits can be reverted.

assert not emitter.should_keep_running()


def test_directory_deleted_event(event_queue, emitter):

@BoboTiG BoboTiG Jan 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, existing tests should fail at some point with those changes, can you fix them instead of adding new ones?

@BoboTiG

BoboTiG commented Feb 20, 2026

Copy link
Copy Markdown
Collaborator

Are you still on it @wingding12?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows/WinAPI: deleted directories result in FileDeletedEvent with is_directory=False (simple fix)

2 participants