fix(scheduler): manual Run-now returns run_ids + fail-fast on offline targets#51
Merged
Conversation
… targets
Root cause: the POST /api/scheduled-tasks/:id/run-now route was
fire-and-forget — it returned 202 immediately with no run_ids, while
the web UI was typed against `{ run_ids: string[] }` and had no way to
know whether a row was actually created. Worse, when the task's target
was offline, the dispatcher silently grace-queued the run (intended for
cron fires, not manual clicks), leaving the user with a button that
appeared to do nothing.
Fix:
- dispatcher.runNow now returns `{ runIds: string[] }` (was void). All
internal early-return paths push their run id before returning.
Backward-compatible: existing callers (catchup, grace, chain) that
ignore the return value still work.
- New `isManual` flag on runNow. When set:
- resolveTargets empty → error='target_offline' (was 'no_targets')
and a `scheduled_run_finished` WS event is broadcast so the
Schedules drawer shows the failure immediately.
- resolved-but-offline target → fail-fast with 'target_offline'
instead of grace-queuing. Manual clicks expect immediate feedback.
- /api/scheduled-tasks/:id/run-now now awaits the dispatcher and
returns `{ ok, status, run_ids }` so the UI can track progress via WS
using the returned ids.
- SchedulesPage logs when dispatch returns zero rows (defensive — the
server now always returns ≥1 row for a found task).
Catchup is untouched (separate owner). Cost-cap behavior preserved
(skipped runs still emit a run row and broadcast the skipped event).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Run now button on the Schedules page silently did nothing when:
{ ok, status }with norun_ids, while the web layer was typed against{ run_ids: string[] }, so it had no run id to track progress with.Root cause
POST /api/scheduled-tasks/:id/run-nowwas fire-and-forget. It returned 202 immediately and calleddispatcher.runNow(...)in the background.runNowreturnedvoid, and offline targets were registered for grace replay (which is correct for cron fires but wrong for manual clicks — the user expects immediate feedback).Fix
dispatcher.runNownow returns{ runIds: string[] }. All early-return paths push their run id before returning. Backward-compatible — existing call sites (catchup,grace,chain) ignore the return value and still work.isManualopt:resolveTargetsempty →error='target_offline'(was'no_targets') and ascheduled_run_finishedWS event fires so the Runs drawer shows the failure immediately.'target_offline'instead of grace-queuing./api/scheduled-tasks/:id/run-nownow awaits the dispatcher and returns{ ok, status, run_ids }.SchedulesPage.handleRunNowlogs when zero rows are returned (defensive).hub/src/scheduler/catchup.tsis intentionally untouched (separate owner per branch coordination).Verification
bun test hub/test/scheduler.test.ts— 52 pass, 0 fail.bun buildof both edited hub modules succeeds.bunx tsc --noEmitonweb/clean.Test plan
run_ids: [<id>]; new row appears in the Runs drawer with status transitioning pending → running → succeeded.run_ids: [<id>]; row appears with statusfailed, errortarget_offline(no 10-minute grace silence).skipped, errordaily_cost_cap(existing behavior, now also returns the run id).🤖 Generated with Claude Code