fix: put the remedy in the warning and match it to how the schedule ticks

This commit is contained in:
hugocasa
2026-08-28 15:06:43 +02:00
parent 63c62ecdad
commit d7617a7599
2 changed files with 24 additions and 23 deletions
+5 -2
View File
@@ -1223,8 +1223,11 @@ async fn fetch_interval_drift(
if !schedule.enabled {
return Ok(None);
}
// Same index, same budget and same `edited_at` bound, for the same reasons as
// the sample in `list_schedule_with_jobs`.
// Query plan: `(workspace_id, runnable_path, created_at DESC)` index, hence the
// `parent_job IS NULL` clause, with the walk capped by DRIFT_SCAN_BUDGET. The
// `edited_at` bound is not that cap: it drops the runs of whatever schedule last
// held this path, which carry the same `trigger`. Nothing writes `edited_at` after
// the insert, so no run of this schedule can predate it.
let push_times = sqlx::query_scalar!(
"SELECT created_at FROM (
SELECT created_at, trigger, trigger_kind
@@ -133,6 +133,10 @@
let no_flow_overlap = $state(false)
// Measured on the deployed schedule, so it is read back rather than edited.
let intervalDrift: ScheduleIntervalDrift | undefined = $state(undefined)
// A flow queues its next run when the previous one starts, and so does a script
// carrying a skip handler, which is pushed as a single step flow. A retry policy
// does not: it is materialized natively onto a plain script job.
let queuesNextRunAtStart = $derived(is_flow || dynamicSkipPath != undefined)
let tag: string | undefined = $state(undefined)
let validCRON = $state(true)
let isValid = $state(true)
@@ -957,6 +961,15 @@
intervalDrift.effective_s * 1000
)} instead of every {msToReadableTime(intervalDrift.configured_s * 1000)}: each of the
last runs was queued too late for the slot that would have kept the cadence.
{#if queuesNextRunAtStart}
Its next run is already queued when the previous one starts, so the runs are
starting late rather than overrunning: look at worker capacity or a concurrency
limit.
{:else}
Script runs never overlap, so a run that outlasts its interval pushes the next one
out. To keep the cadence, schedule a flow instead: a flow queues its next run when
the previous one starts.
{/if}
</Alert>
{/if}
<div class="flex flex-col gap-1">
@@ -1022,27 +1035,12 @@
class="mt-2"
/>
{/if}
{#if itemKind == 'script'}
<!-- A skip handler makes the tick a single step flow, which is armed when the
run starts rather than when it finishes. A retry policy does not: it is
materialized natively onto a plain script job. -->
{@const runsAsFlow = dynamicSkipPath != undefined}
<div class="flex flex-col gap-1 mt-2">
{#if !runsAsFlow}
<Toggle options={{ right: 'no overlap' }} checked={true} disabled />
{/if}
<p class="text-xs text-secondary">
{#if runsAsFlow}
A skip handler makes this schedule run as a single step flow: the next run is
queued when the previous one starts, so runs can overlap.
{:else}
Script runs never overlap: the next run is queued once the previous one has
completed, so a run that outlasts its interval pushes the next one to a later
slot. To keep the configured cadence, schedule a flow instead: a flow starts on
time, and its "no overlap of flows" setting skips a slot while the previous run is
still going.
{/if}
</p>
{#if itemKind == 'script' && !queuesNextRunAtStart}
<div class="flex gap-2 items-center mt-2">
<Toggle options={{ right: 'no overlap' }} checked={true} disabled /><Tooltip
>Script runs never overlap: the next run is queued once the previous one has
completed.</Tooltip
>
</div>
{/if}
{/if}