Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions tests/tools/test_cronjob_run_immediate.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ def test_execute_job_now_bails_without_claim(self):
assert res["success"] is False
m_run.assert_not_called()

def test_execute_job_now_handles_oneshot_removal(self):
"""A one-shot job removed by mark_job_run should report success (#71760).

For finite one-shot jobs (repeat.times=1), mark_job_run removes the
job from the store when completed >= times. get_job returns None in
that case, which previously caused execution_success=False even though
the job ran to completion.
"""
with patch("tools.cronjob_tools.claim_job_for_fire", return_value=True), \
patch("cron.scheduler.run_one_job", return_value=True), \
patch("tools.cronjob_tools.get_job", return_value=None):
res = _execute_job_now(dict(_JOB))
assert res["claimed"] is True
assert res["success"] is True
assert res["error"] is None

def test_execute_job_now_marks_failure_on_exception(self):
"""An exception during fire is captured, marked failed, not propagated."""
with patch("tools.cronjob_tools.claim_job_for_fire", return_value=True), \
Expand Down
13 changes: 12 additions & 1 deletion tools/cronjob_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,18 @@ def _execute_job_now(job: Dict[str, Any]) -> Dict[str, Any]:
# run_one_job records last_run_at/last_status via mark_job_run (which
# also clears the fire claim) and returns True iff it processed the job.
processed = run_one_job(job)
refreshed = get_job(job_id) or {}
refreshed = get_job(job_id)
# For finite one-shot jobs, mark_job_run removes the job from the
# store when completed >= times. get_job returns None in that case,
# so we cannot read last_status — but processed=True means the job
# ran end-to-end successfully. Treat removal as implicit success
# rather than reporting a false "failed" to the operator (#71760).
if refreshed is None:
return {
"claimed": True,
"success": bool(processed),
"error": None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processed is not a success result: run_one_job() returns True for a processed failed job too (cron/scheduler.py:3890-3891), and finite jobs are removed for either terminal outcome (cron/jobs.py:1739-1743). Read the execution ledger here so a removed failed one-shot remains failed and retains its error.

}
ok = refreshed.get("last_status") == "ok"
return {
"claimed": True,
Expand Down
Loading