From 2915cb2113f5c87c3dc2ff6ded483aa5c3bf3141 Mon Sep 17 00:00:00 2001 From: vnz <1267662+vnz@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:13:05 +0100 Subject: [PATCH] Fix failed manual run pushing next_run and premature last_run in UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - record_failure() now only recomputes next_run when the job is already overdue (next_run <= now), preserving the scheduled fire time when a manual run fails before the job's natural next_run - Remove premature job.last_run update in scheduler.js — the job runs asynchronously so last_run should only reflect the server-side completion timestamp on the next data refresh Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/openfang-api/static/js/pages/scheduler.js | 4 +++- crates/openfang-kernel/src/cron.rs | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/openfang-api/static/js/pages/scheduler.js b/crates/openfang-api/static/js/pages/scheduler.js index 94286cdc..8dfb4762 100644 --- a/crates/openfang-api/static/js/pages/scheduler.js +++ b/crates/openfang-api/static/js/pages/scheduler.js @@ -204,7 +204,9 @@ function schedulerPage() { var result = await OpenFangAPI.post('/api/cron/jobs/' + job.id + '/run', {}); if (result.status === 'triggered' || result.status === 'completed') { OpenFangToast.success('Job "' + (job.name || 'job') + '" triggered'); - job.last_run = new Date().toISOString(); + // Don't update job.last_run here — the job runs asynchronously in the + // background. The real last_run is set by the server on completion and + // will appear on the next data refresh. } else { OpenFangToast.error('Run failed: ' + (result.error || 'Unknown error')); } diff --git a/crates/openfang-kernel/src/cron.rs b/crates/openfang-kernel/src/cron.rs index 07c4fedf..27a47daf 100644 --- a/crates/openfang-kernel/src/cron.rs +++ b/crates/openfang-kernel/src/cron.rs @@ -387,7 +387,13 @@ impl CronScheduler { ); meta.job.enabled = false; } else { - meta.job.next_run = Some(compute_next_run_after(&meta.job.schedule, Utc::now())); + // Only recompute next_run if the job was already overdue. This + // preserves the scheduled fire time when a manual (on-demand) run + // fails before the job's natural next_run. + let now = Utc::now(); + if meta.job.next_run.map(|t| t <= now).unwrap_or(true) { + meta.job.next_run = Some(compute_next_run_after(&meta.job.schedule, now)); + } } } }