diff --git a/include/libcamera/internal/device_enumerator_udev.h b/include/libcamera/internal/device_enumerator_udev.h
index c2f7154b6b..3de7c37104 100644
--- a/include/libcamera/internal/device_enumerator_udev.h
+++ b/include/libcamera/internal/device_enumerator_udev.h
@@ -11,6 +11,7 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <unordered_set>
 #include <string>
 #include <sys/types.h>
 
@@ -59,6 +60,7 @@ private:
 	LIBCAMERA_DISABLE_COPY_AND_MOVE(DeviceEnumeratorUdev)
 
 	int addUdevDevice(struct udev_device *dev);
+	void removeUdevDevice(struct udev_device *dev);
 	int populateMediaDevice(MediaDevice *media, DependencyMap *deps);
 	std::string lookupDeviceNode(dev_t devnum);
 
@@ -70,6 +72,7 @@ private:
 	EventNotifier *notifier_;
 
 	std::set<dev_t> orphans_;
+	std::unordered_set<dev_t> devices_;
 	std::list<MediaDeviceDeps> pending_;
 	std::map<dev_t, MediaDeviceDeps *> devMap_;
 };
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
index 4e20a3cc0c..406e59b360 100644
--- a/src/libcamera/device_enumerator_udev.cpp
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -76,6 +76,21 @@ int DeviceEnumeratorUdev::addUdevDevice(struct udev_device *dev)
 	if (!subsystem)
 		return -ENODEV;
 
+	/*
+	 * Record that udev reported the given devnum. And reject if it has already
+	 * been seen (e.g. multiple `enumerate()` calls, device added between udev
+	 * monitor creation in `init()` and `enumerate()`). This record is kept even
+	 * if later in this function an error is encountered. Only a "remove" event from
+	 * udev should erase it from `devices_`.
+	 */
+	const dev_t devnum = udev_device_get_devnum(dev);
+	if (devnum == makedev(0, 0))
+		return -ENODEV;
+
+	const auto [it, inserted] = devices_.insert(devnum);
+	if (!inserted)
+		return -EEXIST;
+
 	if (!strcmp(subsystem, "media")) {
 		std::unique_ptr<MediaDevice> media =
 			createDevice(udev_device_get_devnode(dev));
@@ -111,13 +126,22 @@ int DeviceEnumeratorUdev::addUdevDevice(struct udev_device *dev)
 	}
 
 	if (!strcmp(subsystem, "video4linux")) {
-		addV4L2Device(udev_device_get_devnum(dev));
+		addV4L2Device(devnum);
 		return 0;
 	}
 
 	return -ENODEV;
 }
 
+void DeviceEnumeratorUdev::removeUdevDevice(struct udev_device *dev)
+{
+	const char *subsystem = udev_device_get_subsystem(dev);
+	if (subsystem && !strcmp(subsystem, "media"))
+		removeDevice(udev_device_get_devnode(dev));
+
+	devices_.erase(udev_device_get_devnum(dev));
+}
+
 int DeviceEnumeratorUdev::enumerate()
 {
 	struct udev_enumerate *udev_enum = nullptr;
@@ -341,18 +365,14 @@ void DeviceEnumeratorUdev::udevNotify()
 	}
 
 	std::string_view action(udev_device_get_action(dev));
-	std::string_view deviceNode(udev_device_get_devnode(dev));
 
 	LOG(DeviceEnumerator, Debug)
-		<< action << " device " << deviceNode;
+		<< action << " device " << udev_device_get_devnode(dev);
 
-	if (action == "add") {
+	if (action == "add")
 		addUdevDevice(dev);
-	} else if (action == "remove") {
-		const char *subsystem = udev_device_get_subsystem(dev);
-		if (subsystem && !strcmp(subsystem, "media"))
-			removeDevice(std::string(deviceNode));
-	}
+	else if (action == "remove")
+		removeUdevDevice(dev);
 
 	udev_device_unref(dev);
 }
