How to stop an automation that still has work to do
Retire intake, accepted jobs and external authority in a deliberate order, so a late event cannot revive an obsolete process.
Wednesday’s successful run does not prove Tuesday’s work happened. A period ledger makes missed work selectable, catch-up deliberate and completion something more useful than a green scheduler.
Wednesday’s update succeeds, and the dashboard turns green. Tuesday’s update is still missing. There is no contradiction: the latest run reports that the scheduler can work again, while the business still owes an earlier result. If last_success is the only history available, Wednesday has effectively hidden Tuesday. A separate record of the periods owed gives that missing work somewhere to remain visible after the service recovers.
Publication is the invented example here, but a daily export, expiry check or customer summary can leave the same gap. The schedule says when to try. The service needs to know which periods reached an acceptable outcome, particularly after an outage covered several trigger times. Keeping those two accounts separate also lets recovery run later without pretending that the time of its attempt changes which period the work belongs to.
Kubernetes documents one concrete reason to separate these records: CronJob creation is approximate, and circumstances can produce two jobs or no job, so jobs should be idempotent. [1] The relevant principle also applies to smaller scheduling tools. The trigger attempts to start processing; the application must establish which period that processing concerns and what outcome counts as completing it.
The ledger also needs an expected calendar. A business-day report should not alert for a Saturday it was never meant to produce. Materialise the eligible periods from the agreed schedule or retain enough schedule-version information to derive them consistently. If the calendar later changes, keep the rule that determined Tuesday’s obligation. Otherwise a missing row can remain invisible, or a new schedule can make an old deliberate non-working day look like failed work.
In the teaching ledger, the keys are period and destination. Monday 1 September 2025 has content version m1 and state published; Tuesday 2 September has version t1 and failed_before_effect; Wednesday 3 September has version w1 and published. The actual time of a later attempt is another field. A Thursday recovery run aimed at Tuesday must not acquire Thursday’s identity simply because that is when the process wakes up.
The destination belongs in the key because publishing an update to the site is not the same effect as sending a newsletter. A completed site update should not suppress an owed newsletter if the product intends both. Conversely, two attempts targeting the same Tuesday site publication must agree that they are competing for one operation. PostgreSQL documents multi-column uniqueness and primary keys as ways to enforce such identities in stored rows. [2] The right columns follow the business obligation rather than a generic job identifier.
Keep attempts in a related history. Tuesday’s failed content load and Thursday’s recovery should both remain visible, so support can see unfinished publication with an attempt in progress. A later success should not delete the failure that explains why recovery was needed.
Recovery also needs the intended content version. Tuesday’s t1 draft may have been edited by Wednesday, so a Thursday rerun has a real choice to make. Preserve t1, or record a reviewed change that deliberately replaces it. Otherwise today’s content can acquire Tuesday’s label merely because the recovery process loaded the latest file. For a report, the equivalent decision is whether it reconstructs the period’s original data or gives readers an explicitly identified later view. Keeping that chosen version also lets the operator compare the eventual destination content with the publication that recovery was meant to produce.
The downloadable SQLite example changes Tuesday from failed_before_effect to claimed only while its expected state still holds. The first claim affects one row; the repeat and the attempt to claim published Wednesday affect none. Tuesday keeps its content version, and Monday and Wednesday remain the published periods. The important interval comes next: while Tuesday is claimed, the operator must be able to see an attempt in progress without reading that ownership as evidence that the destination has published anything.
An original three-period publication history separates the scheduled period from the time of an attempt. Thursday’s recovery selects Tuesday, keeping its content version and destination.
Scroll sideways to see all columns.
| Period | Recorded outcome | Thursday decision | What counts as completion |
|---|---|---|---|
| Mon 1 Sep 2025 | Published · version m1 | Skip | Destination reference recorded |
| Tue 2 Sep 2025 | Failed before effect · version t1 | First claim succeeds | Still needs confirmed publication |
| Tue · second claimant | Same period/destination | Claim affects 0 rows | Must not start another effect |
| Wed 3 Sep 2025 | Published · version w1 | Skip | Later success does not fill Tuesday |
| Tue · uncertain remote result | If publishing outcome unknown | Hold for reconciliation | Do not reset to unsent |
When a schedule resumes, choose what missing periods mean. Some work must run once for every period: a ledger export may need each daily file. Some work should collapse into one current result: sending three stale “current availability” summaries can mislead people. Other work should expire: an offer reminder delivered after the offer ends may have no useful purpose. The scheduler cannot choose among those meanings from a cron expression. The period identity remains useful for expired work too: it allows a later review to distinguish an authorised skip from a trigger that never created an obligation record.
For the example, assume Tuesday’s update remains useful and should be published once, explicitly as a delayed update. Thursday’s recovery selects Tuesday’s row, preserves the chosen content version and checks that publication is still permitted. It skips Monday and Wednesday. That selection is a recoverable plan the team can inspect, rather than a broad instruction to rerun everything since the last time somebody remembers it working.
Choose the recovery range before releasing it. A long backlog of summaries may deserve review rather than a burst of stale messages to customers. Record which periods were selected and how their stale material will be handled, while allowing current work to continue where dependencies permit. If catch-up and current work touch the same object, their ordering belongs in that plan. The cron expression cannot tell the application which of those effects should win.
The policy may legitimately leave a period unperformed, but the record should say why: expired under a stated rule, superseded by a current summary, deliberately cancelled or awaiting a person. These are outcomes with meanings, not excuses to mark everything successful. A visible disposition lets the next person distinguish a conscious choice from another silent gap, especially when the absence is discovered weeks later.
A deliberate skip should also preserve who or what policy made it, the policy version and the period it covers. Reopening it later is another decision, rather than a surprising side effect of changing the schedule.
Catch-up meaning follows the work, independently of the scheduler trigger.
Scroll sideways to see all columns.
| Work type in the article | Missing-period policy | What to record |
|---|---|---|
| Daily ledger file | Produce every owed period | Period and destination identity |
| Current availability summary | A single current result may supersede old summaries | Which periods were superseded under which policy |
| Reminder after offer expiry | Expire instead of sending stale work | Explicit expiry disposition |
| Tuesday update in this fixture | Publish once as a delayed update | Preserve selected content version t1 |
A conditional database update can stop a second worker from claiming the same eligible row while the state remains claimed. [3] It cannot make a remote publisher and the local ledger one atomic transaction. The provider might accept Tuesday’s content and then the worker lose the reply before recording success. Resetting that row to failed_before_effect would turn uncertainty into a false assertion that nothing happened.
Keep an explicit unknown-outcome path with the publication reference, stable operation identity or lookup information the destination supports. Query or reconcile before another effect where possible. If the destination offers an appropriate idempotency contract, use it for repeated attempts at this same publication. An expired worker lease is evidence that a local worker stopped renewing ownership; it is not evidence that a remote update never became visible.
Decide what counts as completed at the destination. An API may accept content before it becomes visible, and visibility says nothing about whether a customer read it. Keep the reference and content version with the stage actually confirmed. “Accepted for publication” can be a useful, truthful stopping point for one part of the system. It becomes misleading only when a dashboard quietly promotes it to a promise the provider response never made.
On Thursday, the operator can now ask about Tuesday directly: does it need publication, reconciliation or an explicit expiry decision? The view can show that alongside Wednesday’s success, the age of the gap and any uncertain external effect. Attempts remain useful for finding out why something failed; the period record says whether work is still owed.
Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.
See the Payment Operations PilotPrimary sources behind this article. Living documentation may change.

Practical engineering. A human perspective.
More about Hamsa Software and the person behind it. Editorial notes.