diff --git a/internal/store/cron_store.go b/internal/store/cron_store.go index d84d1f27..ad5f1af9 100644 --- a/internal/store/cron_store.go +++ b/internal/store/cron_store.go @@ -244,11 +244,9 @@ func MergeCronSchedule(current CronSchedule, patch *CronSchedule) CronSchedule { } else if current.Kind == newKind { merged.Expr = current.Expr } - if patch.TZ != "" { - merged.TZ = patch.TZ - } else if current.Kind == newKind { - merged.TZ = current.TZ - } + // TZ: always use patch value. Empty = UTC (default). + // Callers (UI, agent tool) send full schedule via spread, so empty is explicit. + merged.TZ = patch.TZ case "every": if patch.EveryMS != nil { merged.EveryMS = patch.EveryMS diff --git a/internal/store/pg/cron_exec.go b/internal/store/pg/cron_exec.go index ff139b01..fe95d50f 100644 --- a/internal/store/pg/cron_exec.go +++ b/internal/store/pg/cron_exec.go @@ -33,7 +33,7 @@ func (s *PGCronStore) RunJob(ctx context.Context, jobID string, force bool) (boo if parseErr != nil { return false, "", fmt.Errorf("invalid job id %q: %w", jobID, parseErr) } - res, err := s.db.ExecContext(ctx, "UPDATE cron_jobs SET last_status = 'running', next_run_at = NULL, updated_at = $1 WHERE id = $2 AND last_status != 'running'", time.Now(), id) + res, err := s.db.ExecContext(ctx, "UPDATE cron_jobs SET last_status = 'running', next_run_at = NULL, updated_at = $1 WHERE id = $2 AND last_status IS DISTINCT FROM 'running'", time.Now(), id) if err != nil { slog.Warn("cron: failed to claim job for forced run", "jobId", jobID, "error", err) return false, "", err