diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0d890be3..b84127d8 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -94,6 +94,9 @@ The app does not send any sensitive data to cloud providers or similar services.
 	<dependencies>
 		<nextcloud min-version="30" max-version="30" />
 	</dependencies>
+	<background-jobs>
+		<job>OCA\Recognize\BackgroundJobs\MaintenanceJob</job>
+	</background-jobs>
 
 	<repair-steps>
 		<post-migration>
diff --git a/lib/BackgroundJobs/MaintenanceJob.php b/lib/BackgroundJobs/MaintenanceJob.php
new file mode 100644
index 00000000..87b44f60
--- /dev/null
+++ b/lib/BackgroundJobs/MaintenanceJob.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * Copyright (c) 2024 The Recognize contributors.
+ * This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
+ */
+declare(strict_types=1);
+namespace OCA\Recognize\BackgroundJobs;
+
+use OCA\Recognize\Db\FaceDetectionMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DB\Exception;
+use Psr\Log\LoggerInterface;
+
+class MaintenanceJob extends TimedJob {
+
+	public function __construct(
+		ITimeFactory $time,
+		private LoggerInterface $logger,
+		private IJobList $jobList,
+		private FaceDetectionMapper $faceDetectionMapper,
+	) {
+		parent::__construct($time);
+		$this->setInterval(60 * 60 * 12);
+		$this->setTimeSensitivity(self::TIME_INSENSITIVE);
+	}
+
+	/**
+	 * @param mixed $argument
+	 * @return void
+	 */
+	protected function run($argument) {
+		// Trigger clustering in case it's stuck
+		try {
+			$users = $this->faceDetectionMapper->getUsersForUnclustered();
+		} catch (Exception $e) {
+			$this->logger->error($e->getMessage(), ['exception' => $e]);
+			return;
+		}
+		foreach ($users as $userId) {
+			$this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]);
+		}
+	}
+}
diff --git a/lib/Classifiers/Images/ClusteringFaceClassifier.php b/lib/Classifiers/Images/ClusteringFaceClassifier.php
index 115e382b..d3744031 100644
--- a/lib/Classifiers/Images/ClusteringFaceClassifier.php
+++ b/lib/Classifiers/Images/ClusteringFaceClassifier.php
@@ -60,12 +60,12 @@ private function getUsersWithFileAccess(Node $node): array {
 		return array_values(array_unique($userIds));
 	}
 
-    /**
-     * @param string $user
-     * @param \OCA\Recognize\Db\QueueFile[] $queueFiles
-     * @return void
-     * @throws \ErrorException
-     */
+	/**
+	 * @param string $user
+	 * @param \OCA\Recognize\Db\QueueFile[] $queueFiles
+	 * @return void
+	 * @throws \ErrorException
+	 */
 	public function classify(array $queueFiles): void {
 		if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
 			$timeout = self::IMAGE_PUREJS_TIMEOUT;
@@ -94,61 +94,61 @@ public function classify(array $queueFiles): void {
 		}
 
 		$usersToCluster = [];
-        try {
-            $classifierProcess = $this->classifyFiles(self::MODEL_NAME, $filteredQueueFiles, $timeout);
+		try {
+			$classifierProcess = $this->classifyFiles(self::MODEL_NAME, $filteredQueueFiles, $timeout);
 
-            /**
-             * @var list<array> $faces
-             */
-            foreach ($classifierProcess as $queueFile => $faces) {
-                $this->logger->debug('Face results for ' . $queueFile->getFileId() . ' are in');
-                foreach ($faces as $face) {
-                    if ($face['score'] < self::MIN_FACE_RECOGNITION_SCORE) {
-                        $this->logger->debug('Face score too low. continuing with next face.');
-                        continue;
-                    }
-                    if (abs($face['angle']['roll']) > self::MAX_FACE_ROLL || abs($face['angle']['yaw']) > self::MAX_FACE_YAW) {
-                        $this->logger->debug('Face is not straight. continuing with next face.');
-                        continue;
-                    }
+			/**
+			 * @var list<array> $faces
+			 */
+			foreach ($classifierProcess as $queueFile => $faces) {
+				$this->logger->debug('Face results for ' . $queueFile->getFileId() . ' are in');
+				foreach ($faces as $face) {
+					if ($face['score'] < self::MIN_FACE_RECOGNITION_SCORE) {
+						$this->logger->debug('Face score too low. continuing with next face.');
+						continue;
+					}
+					if (abs($face['angle']['roll']) > self::MAX_FACE_ROLL || abs($face['angle']['yaw']) > self::MAX_FACE_YAW) {
+						$this->logger->debug('Face is not straight. continuing with next face.');
+						continue;
+					}
 
-                    try {
-                        $node = $this->rootFolder->getFirstNodeById($queueFile->getFileId());
-                        $userIds = $node !== null ? $this->getUsersWithFileAccess($node) : [];
-                    } catch (InvalidPathException|NotFoundException $e) {
-                        $userIds = [];
-                    }
+					try {
+						$node = $this->rootFolder->getFirstNodeById($queueFile->getFileId());
+						$userIds = $node !== null ? $this->getUsersWithFileAccess($node) : [];
+					} catch (InvalidPathException|NotFoundException $e) {
+						$userIds = [];
+					}
 
-                    // Insert face detection for all users with access
-                    foreach ($userIds as $userId) {
-                        $this->logger->debug('preparing face detection for user ' . $userId);
-                        $faceDetection = new FaceDetection();
-                        $faceDetection->setX($face['x']);
-                        $faceDetection->setY($face['y']);
-                        $faceDetection->setWidth($face['width']);
-                        $faceDetection->setHeight($face['height']);
-                        $faceDetection->setVector($face['vector']);
-                        $faceDetection->setFileId($queueFile->getFileId());
-                        $faceDetection->setUserId($userId);
-                        try {
-                            $this->faceDetections->insert($faceDetection);
-                        } catch (Exception $e) {
-                            $this->logger->error('Could not store face detection in database', ['exception' => $e]);
-                            continue;
-                        }
-                        $usersToCluster[$userId] = true;
-                    }
-                    $this->config->setAppValueString(self::MODEL_NAME . '.status', 'true');
-                    $this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time());
-                }
-            }
-        } finally {
-            $usersToCluster = array_keys($usersToCluster);
-            foreach ($usersToCluster as $userId) {
-                $this->logger->debug('scheduling ClusterFacesJob for user ' . $userId);
-                $this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]);
-            }
-            $this->logger->debug('face classifier end');
-        }
+					// Insert face detection for all users with access
+					foreach ($userIds as $userId) {
+						$this->logger->debug('preparing face detection for user ' . $userId);
+						$faceDetection = new FaceDetection();
+						$faceDetection->setX($face['x']);
+						$faceDetection->setY($face['y']);
+						$faceDetection->setWidth($face['width']);
+						$faceDetection->setHeight($face['height']);
+						$faceDetection->setVector($face['vector']);
+						$faceDetection->setFileId($queueFile->getFileId());
+						$faceDetection->setUserId($userId);
+						try {
+							$this->faceDetections->insert($faceDetection);
+						} catch (Exception $e) {
+							$this->logger->error('Could not store face detection in database', ['exception' => $e]);
+							continue;
+						}
+						$usersToCluster[$userId] = true;
+					}
+					$this->config->setAppValueString(self::MODEL_NAME . '.status', 'true');
+					$this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time());
+				}
+			}
+		} finally {
+			$usersToCluster = array_keys($usersToCluster);
+			foreach ($usersToCluster as $userId) {
+				$this->logger->debug('scheduling ClusterFacesJob for user ' . $userId);
+				$this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]);
+			}
+			$this->logger->debug('face classifier end');
+		}
 	}
 }
diff --git a/lib/Db/FaceDetectionMapper.php b/lib/Db/FaceDetectionMapper.php
index 672143d2..75faf836 100644
--- a/lib/Db/FaceDetectionMapper.php
+++ b/lib/Db/FaceDetectionMapper.php
@@ -306,6 +306,24 @@ public function countUnclustered(): int {
 		return (int) $count;
 	}
 
+	/**
+	 * @return array<string>
+	 * @throws \OCP\DB\Exception
+	 */
+	public function getUsersForUnclustered(): array {
+		$qb = $this->db->getQueryBuilder();
+		$qb->selectDistinct('user_id')
+			->from('recognize_face_detections')
+			->where($qb->expr()->isNull('cluster_id'))
+			->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)))
+			->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)));
+		$result = $qb->executeQuery();
+		/** @var array<string> $users */
+		$users = $result->fetchAll(\PDO::FETCH_COLUMN);
+		$result->closeCursor();
+		return $users;
+	}
+
 	protected function mapRowToEntity(array $row): Entity {
 		try {
 			return parent::mapRowToEntity($row);