Skip to content

fix(cron): treat non-dict origin as missing instead of crashing tick - #19283

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-c5c32519
May 3, 2026
Merged

fix(cron): treat non-dict origin as missing instead of crashing tick#19283
teknium1 merged 1 commit into
mainfrom
hermes/hermes-c5c32519

Conversation

@teknium1

@teknium1 teknium1 commented May 3, 2026

Copy link
Copy Markdown
Contributor

Salvage of #19013 by @Tranquil-Flow onto current main. Closes the non-dict-origin sub-bug from #18722 where a job whose origin field holds a free-form string (e.g. migration tag) crashes every tick with AttributeError: 'str' object has no attribute 'get' and never recovers.

Changes

  • cron/scheduler.py: _resolve_origin guards isinstance(origin, dict) instead of bare truthiness (+11/-2)
  • tests/cron/test_scheduler.py: parametrized test_non_dict_origin_returns_none_instead_of_crashing over str/int/list/tuple/float (+24)

Validation

  • TestResolveOrigin: 10/10 pass
  • 3 TestSilentDelivery failures are pre-existing on current main (same failures without this change), unrelated to cron origin handling

Closes #19013. Related #18722. Sibling sub-bug (recurring-job recovery) tracked in #18825 as the PR notes.

``_resolve_origin`` called ``origin.get('platform')`` on whatever
``job.get('origin')`` returned. The leading ``if not origin: return None``
short-circuited the falsy cases (None, empty dict, "") but a non-empty
string passed that guard and then crashed with
``AttributeError: 'str' object has no attribute 'get'`` on every fire
attempt. Observed in the wild after a migration script tagged jobs with
free-form provenance strings (e.g.
``"combined-digest-replaces-x-and-y-20260503"``).

``mark_job_run`` did record ``last_status: error,
last_error: "'str' object has no attribute 'get'"`` once, but the next
tick re-loaded the same poisoned origin and crashed identically. The
job stayed enabled, fired every tick, and accumulated cascading errors
in the log until ``origin`` was patched manually.

Replace the falsy guard with ``isinstance(origin, dict)``. Non-dict
origins (string, int, list, tuple, float — anything that survived a
hand-edit, JSON-script write, or migration) are now treated the same
as a missing origin: the job continues with ``deliver`` falling back
through its normal home-channel path instead of crashing the scheduler
loop.

Test parametrises the non-dict shapes that can appear in jobs.json
through external writers and asserts ``_resolve_origin`` returns None
for each.

Note: this fix scope is the non-dict-``origin`` crash only. The
``next_run_at: null`` recurring-job recovery (the second sub-bug in
#18722) is independently addressed by the in-flight #18825, which
extends the never-silently-disable defense from #16265 to
``get_due_jobs()`` — that approach is well-aligned with the existing
recovery pattern and ships fine without a competing change here.

Fixes #18722 (non-dict origin crash; recurring-job recovery covered by #18825)
@teknium1
teknium1 merged commit 6b4fb9f into main May 3, 2026
9 of 10 checks passed
@teknium1
teknium1 deleted the hermes/hermes-c5c32519 branch May 3, 2026 15:51
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management labels May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants