From f90a187a159565f782ed3c22a68332c93718c245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Sun, 21 Jun 2026 23:11:54 +0200 Subject: [PATCH] fix(backgroundjob): don't log ERROR for stale job rows of removed apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JobList::buildJob() logged a full ERROR-level stack trace via logException() whenever a job row referenced a class that no longer exists — e.g. a stale oc_jobs row left behind by a removed or disabled app (the "SyncJob does not exist" spam in issue #35589). The job was already correctly skipped (return null), but the noisy ERROR log recurred on every cron run for a benign, expected condition. Check class_exists() first: a class that exists but fails to resolve as a service is a genuine, actionable DI failure and still logs at ERROR via logException(); a class that no longer exists (stale row) is now logged once at DEBUG and skipped. Control flow and return values are unchanged. Updates the existing JobListTest case accordingly: a missing-class job must not call logException and instead logs at debug. Fixes #35589 Co-Authored-By: Claude Opus 4.8 Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> --- lib/private/BackgroundJob/JobList.php | 13 +++++++++++-- tests/lib/BackgroundJob/JobListTest.php | 10 ++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index e37b1db0ba70..a759632685ee 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -281,12 +281,21 @@ private function buildJob($row) { /** @var IJob $job */ $job = \OC::$server->query($row['class']); } catch (QueryException $e) { - $this->logger->logException($e, ['app' => 'core']); if (\class_exists($row['class'])) { + // The class exists but could not be resolved as a service: + // this is a genuine, actionable DI failure -> log at ERROR. + $this->logger->logException($e, ['app' => 'core']); $class = $row['class']; $job = new $class(); } else { - // job from disabled app or old version of an app, no need to do anything + // The class does not exist anymore (stale job row from a + // disabled/removed app or an old version). This is a benign, + // expected condition, so only log it at DEBUG instead of + // spamming an ERROR-level stack trace on every cron run. + $this->logger->debug( + 'Background job class ' . $row['class'] . ' does not exist (stale job row), skipping', + ['app' => 'core'] + ); return null; } } diff --git a/tests/lib/BackgroundJob/JobListTest.php b/tests/lib/BackgroundJob/JobListTest.php index 8ab6b24a23ba..2f88b2c021b0 100644 --- a/tests/lib/BackgroundJob/JobListTest.php +++ b/tests/lib/BackgroundJob/JobListTest.php @@ -285,9 +285,15 @@ private function addWrongJob() { $query->execute(); } - public function testUnknownJobLogsException() { + public function testUnknownJobDoesNotLogException() { + // A stale job row whose class no longer exists must NOT be logged at + // ERROR level via logException (see issue #35589). It is a benign, + // expected condition and is only logged at DEBUG instead. $this->addWrongJob(); - $this->logger->expects($this->once())->method('logException'); + $this->logger->expects($this->never())->method('logException'); + $this->logger->expects($this->atLeastOnce()) + ->method('debug') + ->with($this->stringContains('wrong job title'), ['app' => 'core']); $this->instance->listJobs(function () { }); $this->clearJobsList();