fix(actions): run every due schedule exactly once per occurrence - #39078
Conversation
A per-spec failure returned from the schedule task loop, so a single workflow that could not be turned into a run stopped every remaining due schedule. The spec's next run time was not advanced either, leaving the same failure to repeat on every pass. Handle each spec on its own: log the failure, keep going, and always move the spec to its next occurrence.
There was a problem hiding this comment.
Pull request overview
This PR adjusts the Actions scheduler cron pass so one broken scheduled workflow no longer aborts processing for all other due schedules, and adds regression coverage to ensure valid schedules still run while failing ones don’t block the pass.
Changes:
- Refactors the scheduler loop to process each due schedule spec independently, logging per-spec failures instead of aborting the full pass.
- Ensures schedule specs advance to their next occurrence even when run creation fails (to prevent repeated once-per-minute failures).
- Adds a unit test to verify a failing spec doesn’t prevent other specs from being scheduled and that both specs’
Nexttimestamps advance.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| services/actions/schedule_tasks.go | Refactors scheduled-run processing into a per-spec helper and changes failure handling so the cron pass continues. |
| services/actions/schedule_tasks_test.go | Adds a regression test ensuring one broken spec doesn’t block others and specs advance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Advancing first makes the "always advanced" guarantee hold, which the skip paths broke, and stops a failed spec update from inserting the same run again on the next pass. Two more ways the loop mishandled due specs are fixed as well. Offset paging skipped specs, because advancing them drops them out of the "next <= now" filter. A valid but unsatisfiable cron such as "0 0 30 2 *" stored a negative next that stayed due forever and created a run on every pass. Per-spec failures are now counted and reported once per pass, so the cron task still records an admin notice for them. Assisted-by: Claude Code:claude-opus-5
| Next: now.Unix(), | ||
| ListOptions: db.ListOptions{Page: 1, PageSize: pageSize}, | ||
| Next: now.Unix(), | ||
| BeforeID: beforeID, |
There was a problem hiding this comment.
You have refactored "db.Iterate" recently.
- fix(db): make paginated database reads always require "order" option - fix(db): make paginated database reads always require "order" option #39017
There was a problem hiding this comment.
So, can you or your AI remember the useful information? If your AI can't remember, then human should remember.
There was a problem hiding this comment.
The best/only way to remember is to put it in docs.
There was a problem hiding this comment.
Since you refuse to read and understand code, how do you know what should be put into docs? Whether the contents docs are still correct?
There was a problem hiding this comment.
Replaced with db.Iterate — it already does this keyset walk
Add comment clarifying behavior for archived repositories. Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
A schedule whose workflow could not be turned into a run returned an error out of the "Start actions schedule tasks" loop, so every remaining due schedule in that pass was skipped. The spec's next run time was not advanced either, so the same failure repeated once a minute, each time writing an admin notice.
Each spec is now advanced to its next occurrence before its run is created. A failure costs that one occurrence instead of the whole pass, and a failed update can no longer insert the same run twice. Failures are counted and reported once per pass, so the admin notice appears per occurrence rather than per minute.
Two related bugs in the same loop:
next <= nowafter advancing the previous page, so above 50 due specs half of them were skipped until the next pass. It now pages by id.0 0 30 2 *stores a negativenext, which stayed due forever and created a run on every pass. Those specs are no longer inserted or selected.The offending workflow itself is still rejected and logged rather than recorded as a failed run. Persisting a startup failure as a visible run is a separate change, since the same code path also covers cross-repo permission denials, where not persisting a run is intentional.
Fixes #39065