Pulling data in pages of 50/100/250 was never the hard part. The hard part was cron scheduling certainty.
In older versions, if a cron timed out mid-run, it would usually only continue on the next scheduled execution. That meant:
- If you scheduled the job twice a day, a timeout at 09:00 could mean “continue at 21:00”… not great.
- To compensate, many teams scheduled the job every hour (or even every few minutes) because they expected interruptions.
- Or you wrote custom logic to manipulate
nextcall/ force follow-up runs.
Odoo 19 introduces the missing primitive: a way for a cron to report progress in a way the scheduler understands, so Odoo can decide “do I need to keep running soon to finish?”
That primitive is:
ir.cron._commit_progress()
The real feature: scheduling certainty, not paging
The real win is: you can keep a sensible schedule (e.g., twice a day) and still be confident the job will complete.
Because if the job can’t finish within a single execution window, it can:
- checkpoint progress
- stop cleanly
- and Odoo will re-trigger it quickly until it’s done
So you get:
- “run twice a day” as your intent
- “continue until complete” as actual behavior
The new primitive: _commit_progress()
This is the method that changes the game:
@api.model
def _commit_progress(
self,
processed: int = 0,
*,
remaining: int | None = None,
deactivate: bool = False,
) -> float:
"""
Commit and log progress for the batch from a cron function.
The number of items processed is added to the current done count.
If you don't specify a remaining count, the number of items processed
is subtracted from the existing remaining count.
If called from outside the cron job, the progress function call will
just commit.
:param processed: number of processed items in this step
:param remaining: set the remaining count to the given count
:param deactivate: deactivate the cron after running it
:return: remaining time (seconds) for the cron run
"""
What it gives you:
- A standard way to say: “I processed X items”
- Optionally: “I still have Y remaining”
- A return value (
time_left) so you can stop gracefully before timeout - Most importantly: it enables Odoo to know whether the cron should keep going soon or can wait until the next planned schedule
How this looks in a Shopify order pull cron
1) Start the run by declaring the workload (remaining)
The point isn’t “counting is fancy” — it’s giving Odoo the signal: this job has work to finish.
remaining_count = self._get_shopify_orders_count(api_url, access_token, since_id, start_date)
if remaining_count == 0:
_logger.info("No orders to pull from Shopify")
return
self.env['ir.cron']._commit_progress(remaining=remaining_count)
2) Process one page (your existing paging stays)
orders = self._fetch_shopify_orders(api_url, access_token, since_id, start_date, page_limit)
if not orders:
break
3) After each page, checkpoint progress (processed)
This is the new part that turns “paging” into “guaranteed completion”.
time_left = self.env['ir.cron']._commit_progress(processed=len(orders))
4) If time is running out, stop cleanly — and let Odoo continue soon
if not time_left:
_logger.info("Cron approaching timeout, stopping gracefully")
return
Why this changes how you schedule crons
Before Odoo 19
- If you scheduled the cron twice a day and it timed out, it might not continue for hours.
- So you scheduled it every hour (or more frequently), or you hacked the scheduling logic.
With Odoo 19
- You can schedule it for business reasons (“twice a day”).
- And still rely on Odoo to “finish the job” by re-running quickly when needed,
because the cron itself reports progress via
_commit_progress().
In practice, that means fewer cron runs overall, less noise, and far fewer “why didn’t it finish?” incidents.
Final takeaway
Coding crons always has some challenges but with this new method we can say:
“I’m not done yet — keep me running soon until completion.”