Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

GitHub Actions Scheduling: Match the Trigger Before the Clock

GitHub Actions Scheduling: Match the Trigger Before the Clock A scheduled worker can run successfully and still publish at the wrong time. One subtle cause is a workflow that contains two cron expressions for each desi

GitHub Actions Scheduling: Match the Trigger Before the Clock

A scheduled worker can run successfully and still publish at the wrong time. One subtle cause is a workflow that contains two cron expressions for each desired local time: one for summer and one for winter. Checking whether the current clock is near a publishing slot does not prove that the expression which triggered the run belongs to the current season.

This guide uses a small publishing worker as an example. The design is deliberately limited to fixed daily slots in Europe/London. It is not a general calendar library, and workflows involving several countries or arbitrary recurrence rules need a broader scheduling model.

Understand the two different times

Record both the scheduled occurrence and the actual execution time. They answer different questions. The scheduled occurrence identifies the work the runner intended to process. The execution time tells you how late the runner started.

Suppose a workflow is intended to run at 17:20 local time. A second seasonal expression fires an hour later. If that run is delayed again, a nearest-slot check might associate it with a different publishing slot. A tolerance window makes that mistake easier to miss.

Instead, first determine whether the triggering expression represents a valid local slot under the current timezone offset. Only then decide whether the execution is still timely enough to process it.

Filter the alternate seasonal expression

Keep a small explicit set of allowed local times. Convert the hour and minute in the triggering cron expression into the local hour and minute for the relevant date. Reject the expression if that local time is not in the allowed set.

For example, our fixed daily worker uses this decision structure:

function acceptOccurrence({ targetMinutes, allowedMinutes, nowMinutes }) {
  if (!allowedMinutes.has(targetMinutes)) return false;
  const delay = nowMinutes - targetMinutes;
  return delay >= 0 && delay <= 80;
}

The example assumes that timezone conversion has already happened and that the occurrence and execution are on the same local day. Do not copy it unchanged into a workflow that can cross midnight. Store a full timestamp for the intended occurrence in that case.

The important order is expression validation first, lateness validation second. Merely widening the tolerance window does not solve the seasonal problem.

Make the lateness policy explicit

A delayed run creates a product decision. Should a post appear late, be skipped, or wait for a later slot? Write that policy down before choosing a number in the workflow.

In our example, adjacent slots are more than 90 minutes apart, so an 80-minute allowance stays within the gap. That allowance is a chosen policy, not a platform guarantee. Another schedule with closer slots would require a smaller window or stronger occurrence tracking.

Reject early execution as well as excessively late execution. A negative delay can indicate that the wrong seasonal trigger was matched to an upcoming slot. Do not treat an early run as an opportunity to publish ahead of schedule.

Keep scheduling separate from content eligibility

Passing the time gate should not be the only permission to publish. The content still needs editorial approval, the correct destination account and an unpublished delivery record.

Likewise, a valid publishing slot with an empty reviewed queue is not an authentication failure. Log it as a content-supply issue. Operators need that distinction to decide whether to prepare more content or repair credentials.

Use a separate read-only mode to check account authentication and queue selection. A manual diagnostic run should not silently become a public post merely because a repository variable enables scheduled publishing.

Test boundaries with fixed timestamps

Clock-dependent code is easier to test when it accepts a date argument. Avoid tests that depend on the current machine time. Include a summer date, a winter date, the exact slot, one minute before it and one minute beyond the permitted lateness.

Also test the alternate seasonal expression on each date. A test suite that only verifies the correct expression can miss the duplicate-trigger problem completely.

For a production calendar spanning clock changes, add cases on both transition days and document whether a repeated or missing local time should execute. The simple same-day design here assumes ordinary daytime slots. Its scope should remain visible to anyone who extends it.

Report why a run did nothing

Use distinct messages for an alternate seasonal trigger, an expired publishing window and an empty queue. All three can legitimately avoid publishing, but they are different operational facts.

After publication, record the remote post ID and public URL. A scheduler badge only describes job completion. It does not establish that the intended content reached the intended account at the intended time.

That separation makes the automation easier to operate: the scheduler identifies an occurrence, the worker verifies eligibility, and the delivery record supplies evidence of publication. Each stage can then be checked and repaired without guessing what a successful exit code meant.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.