From 91bcf88df9fcb2cb75e4fa1a913509c90bae9b5f Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 1 Sep 2026 01:26:15 -0600 Subject: [PATCH 1/2] fix: use updateJob to replace repeatable schedule job on update in cloud MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #5233. Updating an enabled schedule (cron/timezone change) called schedule() (create-backup), which only adds a new repeatable job and never removes the one registered under the old cron pattern, leaving an orphaned BullMQ job running in Redis indefinitely. Switched to updateJob() (update-backup), which looks up the currently registered repeatable pattern, removes it, then re-adds — same pattern already used in backup.ts for backups. --- apps/dokploy/server/api/routers/schedule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts index d69760124..8629d7546 100644 --- a/apps/dokploy/server/api/routers/schedule.ts +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -25,7 +25,7 @@ import { asc, desc, eq } from "drizzle-orm"; import { z } from "zod"; import { audit } from "@/server/api/utils/audit"; import { assertScheduledJobLimit } from "@/server/api/utils/plan-limits"; -import { removeJob, schedule } from "@/server/utils/backup"; +import { removeJob, schedule, updateJob } from "@/server/utils/backup"; import { createTRPCRouter, protectedProcedure } from "../trpc"; export const scheduleRouter = createTRPCRouter({ @@ -130,7 +130,7 @@ export const scheduleRouter = createTRPCRouter({ if (IS_CLOUD) { if (updatedSchedule?.enabled) { - schedule({ + await updateJob({ scheduleId: updatedSchedule.scheduleId, type: "schedule", cronSchedule: updatedSchedule.cronExpression, From 4e38925119cc22d84cc8b0822aff58ea2b9b48a5 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 1 Sep 2026 01:31:59 -0600 Subject: [PATCH 2/2] fix: pass timezone when removing repeatable schedule job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit removeJob() for type 'schedule' computed the BullMQ repeatable key using only the cron pattern, without the timezone. scheduleJob() always registers the repeatable job with tz set (job.timezone || UTC), so BullMQ's repeat key (name:jobId:endDate:tz:pattern) never matched on removal — removeRepeatable silently no-op'd and the job kept firing forever, surviving disable, delete, and reboots. Forward timezone through removeJob (queue.ts), the /update-backup lookup (index.ts), and the disable/delete call sites in the schedule router. --- apps/dokploy/server/api/routers/schedule.ts | 2 ++ apps/schedules/src/index.ts | 1 + apps/schedules/src/queue.ts | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts index 8629d7546..a7417a92c 100644 --- a/apps/dokploy/server/api/routers/schedule.ts +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -141,6 +141,7 @@ export const scheduleRouter = createTRPCRouter({ cronSchedule: updatedSchedule.cronExpression, scheduleId: updatedSchedule.scheduleId, type: "schedule", + timezone: updatedSchedule.timezone, }); } } else { @@ -185,6 +186,7 @@ export const scheduleRouter = createTRPCRouter({ cronSchedule: scheduleItem.cronExpression, scheduleId: scheduleItem.scheduleId, type: "schedule", + timezone: scheduleItem.timezone, }); } else { removeScheduleJob(scheduleItem.scheduleId); diff --git a/apps/schedules/src/index.ts b/apps/schedules/src/index.ts index a5f0fd47c..28eacbe1c 100644 --- a/apps/schedules/src/index.ts +++ b/apps/schedules/src/index.ts @@ -60,6 +60,7 @@ app.post("/update-backup", zValidator("json", jobQueueSchema), async (c) => { scheduleId: data.scheduleId, type: "schedule", cronSchedule: job.pattern || "", + timezone: job.tz || data.timezone, }); } else if (data.type === "volume-backup") { result = await removeJob({ diff --git a/apps/schedules/src/queue.ts b/apps/schedules/src/queue.ts index 7b0c48315..3d9b2c802 100644 --- a/apps/schedules/src/queue.ts +++ b/apps/schedules/src/queue.ts @@ -66,9 +66,10 @@ export const removeJob = async (data: QueueJob) => { return result; } if (data.type === "schedule") { - const { scheduleId, cronSchedule } = data; + const { scheduleId, cronSchedule, timezone } = data; const result = await jobQueue.removeRepeatable(scheduleId, { pattern: cronSchedule, + tz: timezone || "UTC", }); return result; }