Fixing inotify_add_watch errors when deleting files with QFileSystemModel

Why Qt's file watcher complains about deleted directories and how to handle it
Heads up! You've already completed this tutorial.

I have a GUI with a tree view file explorer using QFileSystemModel. When I delete a project directory, I get inotify_add_watch errors like "No such file or directory". The same deletion from the command line doesn't produce these errors. What's going on, and how do I fix it?

If you've built a file explorer using QFileSystemModel and QTreeView in PyQt6, you may have run into a confusing situation: deleting a directory works, but Qt spits out warnings about inotify_add_watch failing because the path no longer exists. This doesn't happen when you delete the same directory from a terminal, so the problem clearly has something to do with how Qt watches the file system.

Let's look at why this happens and how to handle it cleanly.

What is inotify?

On Linux, inotify is a kernel subsystem that lets applications monitor file system events — things like files being created, modified, or deleted. When you use QFileSystemModel, Qt sets up inotify watches on directories so it can automatically update the model (and your tree view) when files change on disk.

This is what makes QFileSystemModel feel "live" — if you create a file in a watched directory outside of your app, the tree view updates automatically.

Why the error appears

The inotify_add_watch error happens because of a timing issue between your deletion code and Qt's file system watcher. Here's the typical sequence:

  1. Your code deletes a directory (and its contents).
  2. The inotify watcher notices that something changed and fires an event.
  3. Qt's internal file watcher tries to re-watch the directory or its subdirectories.
  4. The directory no longer exists, so inotify_add_watch fails and Qt logs the warning.

The root cause is that QFileSystemModel is still tracking those paths at the moment the deletion happens. Qt's watcher doesn't know in advance that you are the one removing the directory — it just sees changes and tries to keep up.

When you delete from the command line, no Qt watcher is involved, so there's no conflict.

The solution: remove the path from the model first

The cleanest way to avoid these warnings is to tell the QFileSystemModel to stop watching the relevant paths before you delete them from disk. You can do this by working with the model's root path and the QFileSystemWatcher that Qt maintains internally.

However, QFileSystemModel doesn't directly expose its internal QFileSystemWatcher. Instead, there are a couple of practical approaches you can use.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

Get the book

Approach 1: Use shutil.rmtree with a small delay

One straightforward approach is to remove the item from the tree view's selection and then use QTimer.singleShot to delay the actual file deletion slightly. This gives QFileSystemModel a moment to process the change:

python
import sys
import shutil
from pathlib import Path

from PyQt6.QtCore import QTimer
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QTreeView,
    QVBoxLayout,
    QWidget,
    QPushButton,
    QFileSystemModel,
    QMessageBox,
)


class FileExplorer(QMainWindow):
    def __init__(self, root_path):
        super().__init__()
        self.setWindowTitle("File Explorer")
        self.resize(600, 400)

        self.model = QFileSystemModel()
        self.model.setRootPath(root_path)

        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(root_path))

        self.delete_button = QPushButton("Delete Selected")
        self.delete_button.clicked.connect(self.delete_selected)

        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        layout.addWidget(self.delete_button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def delete_selected(self):
        indexes = self.tree.selectedIndexes()
        if not indexes:
            return

        # Get the file path from the first column index.
        index = indexes[0]
        file_path = self.model.filePath(index)

        if not file_path:
            return

        reply = QMessageBox.question(
            self,
            "Confirm Delete",
            f"Delete '{file_path}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )

        if reply == QMessageBox.StandardButton.Yes:
            self.perform_delete(file_path)

    def perform_delete(self, file_path):
        path = Path(file_path)

        # Remove the index from the model first, so Qt stops watching it.
        index = self.model.index(file_path)
        if index.isValid():
            self.model.remove(index)

        # Use a short delay to let the model process the removal
        # before cleaning up anything left on disk.
        QTimer.singleShot(100, lambda: self.cleanup_path(path))

    def cleanup_path(self, path):
        """Remove any remaining files/directories from disk."""
        if path.exists():
            if path.is_dir():
                shutil.rmtree(path)
            else:
                path.unlink()


app = QApplication(sys.argv)

# Change this to a directory you want to browse.
root = str(Path.home())

window = FileExplorer(root)
window.show()

sys.exit(app.exec())

The self.model.remove(index) call tells QFileSystemModel to delete the file or directory itself. This is the preferred way to delete through the model because Qt handles the watcher cleanup internally. The cleanup_path method acts as a safety net — if model.remove() didn't fully remove a directory tree (for example, if it contained build artifacts that made removal partial), shutil.rmtree finishes the job after a brief delay.

Approach 2: Use QFileSystemModel.remove() directly

If your directories are simple (no deeply nested build output), you can rely entirely on QFileSystemModel.remove():

python
def delete_selected(self):
    indexes = self.tree.selectedIndexes()
    if not indexes:
        return

    index = indexes[0]
    file_path = self.model.filePath(index)

    reply = QMessageBox.question(
        self,
        "Confirm Delete",
        f"Delete '{file_path}'?",
        QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
    )

    if reply == QMessageBox.StandardButton.Yes:
        success = self.model.remove(index)
        if not success:
            QMessageBox.warning(
                self,
                "Delete Failed",
                f"Could not delete '{file_path}'. "
                "The directory may not be empty.",
            )

This is the simplest option. QFileSystemModel.remove() returns True if the deletion succeeded and False if it didn't. Because the model is handling the deletion itself, it can coordinate with its internal file watcher, and you won't see the inotify_add_watch errors.

The limitation is that remove() may fail on non-empty directories, depending on the platform and Qt version. If you're working with project directories that contain build output, compiled files, or nested subdirectories, you may need the combined approach from the first example.

When you need shutil.rmtree

If you must use shutil.rmtree (because the directory tree is complex or contains files that QFileSystemModel.remove() can't handle), the pattern is:

  1. Clear the selection in the tree view so the view doesn't try to access the deleted index.
  2. Change the model's root path if the deleted directory is currently being watched as a root.
  3. Delete the files with shutil.rmtree.

Here's how that looks in practice:

python
def perform_delete(self, file_path):
    path = Path(file_path)

    # Clear the selection to prevent the view from accessing stale indexes.
    self.tree.clearSelection()

    # If deleting the currently-rooted directory, move the root up first.
    current_root = self.model.rootPath()
    if file_path.startswith(current_root) or file_path == current_root:
        parent_dir = str(path.parent)
        self.model.setRootPath(parent_dir)
        self.tree.setRootIndex(self.model.index(parent_dir))

    # Now delete from disk.
    if path.is_dir():
        shutil.rmtree(path)
    else:
        path.unlink()

By moving the model's root path away from the directory you're about to delete, you reduce the chance that Qt's watcher will try to re-watch paths that no longer exist.

Summary

The inotify_add_watch warnings appear because QFileSystemModel's internal file watcher is still tracking directories at the moment they're deleted from disk. To avoid this:

  • Prefer QFileSystemModel.remove() to let Qt handle both the deletion and the watcher cleanup.
  • If you need shutil.rmtree, clear the tree view's selection and adjust the model's root path before deleting.
  • Use QTimer.singleShot as a safety net if you need to clean up files that the model's remove() didn't catch.

These warnings are cosmetic — they don't usually cause crashes or data loss — but handling them properly makes your application more robust and avoids confusing log output for your users (and for you during development).

If you're building a file explorer interface with QTreeView, you might also find our QTreeView tutorial helpful for understanding model-view patterns with tree structures. For more on Qt's model-view architecture in general, see our guide to PyQt6 ModelView architecture. And if you're looking to run external deletion commands or scripts alongside your GUI, take a look at our QProcess tutorial for PyQt6 which covers running external programs without blocking the UI.

1:1 Coaching & Tutoring for your Python GUIs project
Martin Fitzpatrick Python GUIs Coaching & Training
60 mins ($195) Book Now

1:1 Python GUIs Coaching & Training

Comprehensive code reviewBugfixes & improvements • Maintainability advice and architecture improvements • Design and usability assessment • Suggestions and tips to expand your knowledgePackaging and distribution help for Windows, Mac & Linux • Find out more.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Fixing inotify_add_watch errors when deleting files with QFileSystemModel was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.