From 9be99059ddd170cfca13ceffb482e9b1c124be9a Mon Sep 17 00:00:00 2001 From: "molecule-ai[bot]" <276602405+molecule-ai[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 03:07:26 +0000 Subject: [PATCH] fix(scheduler): use context.Background() for post-fire UPDATE (F1089) (#1244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-fire UPDATE after s.proxy.ProxyA2ARequest() was using fireCtx, which derives from the outer ctx passed into fireSchedule(). If that ctx is cancelled — HTTP timeout, graceful shutdown, or any upstream deadline — ExecContext returns context.Canceled and the UPDATE is silently skipped, leaving next_run_at stale and causing the schedule to re-fire on the next tick. Fix: create a dedicated updateCtx from context.Background() with a 5s deadline, independent of the outer ctx hierarchy. Also improved the error log to include schedule name for easier debugging. Complements PR #1241 (fix/f1089-scheduler-ctx-fix-main) which fixes the goroutine-panic path in tick() — this fix covers the wider case of normal-return + ctx-cancelled after the proxy call. F1089 | Severity: HIGH+security Co-authored-by: Molecule AI Infra Lead Co-authored-by: Claude Sonnet 4.6 --- workspace-server/internal/scheduler/scheduler.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/workspace-server/internal/scheduler/scheduler.go b/workspace-server/internal/scheduler/scheduler.go index 7dcf61a1..dc18dece 100644 --- a/workspace-server/internal/scheduler/scheduler.go +++ b/workspace-server/internal/scheduler/scheduler.go @@ -389,7 +389,15 @@ func (s *Scheduler) fireSchedule(ctx context.Context, sched scheduleRow) { sched.Name, sched.ID, nextErr) } - _, err := db.DB.ExecContext(ctx, ` + // F1089: use a dedicated context with its own 5s deadline for the + // post-fire UPDATE. The outer ctx (fireCtx) may be cancelled if the + // HTTP call timed out or the server is shutting down; using it here + // would silently skip the UPDATE and leave next_run_at stale, causing + // the schedule to be immediately re-fired on the next tick. + updateCtx, updateCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer updateCancel() + + _, err := db.DB.ExecContext(updateCtx, ` UPDATE workspace_schedules SET last_run_at = now(), next_run_at = COALESCE($2, next_run_at), @@ -400,7 +408,7 @@ func (s *Scheduler) fireSchedule(ctx context.Context, sched scheduleRow) { WHERE id = $1 `, sched.ID, nextRunPtr, lastStatus, lastError) if err != nil { - log.Printf("Scheduler: update error for %s: %v", sched.ID, err) + log.Printf("Scheduler: post-fire update error for %s [%s]: %v", sched.ID, sched.Name, err) } // Log a dedicated cron_run activity entry with schedule metadata so the