Skip to content

fix(cron): surface missed-fire catch-up lateness in hermes cron list/status - #100445

Merged
teknium1 merged 1 commit into
mainfrom
fix/99879-cron-missed-run-visibility
Sep 1, 2026
Merged

teknium1 merged 1 commit into
mainfrom
fix/99879-cron-missed-run-visibility

Conversation

@teknium1

@teknium1 teknium1 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Addresses the visibility half of #99879 (per the triage comment: item 2, the highest-leverage residual). The catch-up mechanism already existed; this PR makes late catch-up runs visible in the CLI instead of rendering as ordinary on-time successes.

What changed

  • cron/jobs.py — the due-scan (_get_due_jobs_locked) now persists a last_dispatch stamp on every recurring dispatch: scheduled_at, dispatched_at, lateness_seconds, and kind (on_time / late / catch_up, classified against a 5-min ticker tolerance and the schedule's existing catch-up grace window). Manual triggers aren't stamped (no scheduled instant to be late against); one-shots beyond grace are already retired. The stamp is written under the same jobs lock + save_jobs pass the due-scan already holds — no new locking, no schema/state.db change (jobs.json field only).
  • hermes_cli/cron.py —
    • hermes cron list renders a per-job Dispatch: line: dim on time (scheduled ...) for normal fires, loud ⚠ catch-up after missed fire: scheduled 2026-08-31T09:00:00, ran 2026-09-01T08:20:38 (23h 20m late) for late/catch-up fires.
    • hermes cron status gains a late-jobs callout (⚠ N job(s) last fired late (missed-fire catch-up): ...) in the shared active-jobs summary, so it covers both the built-in ticker and external-provider status paths.

Scope discipline: CLI output + the one minimal persisted field needed to display it. No new tools, no missed-run policy engine.

Live repro: real hermes cron create + backdated next_run_at (daily job 23h overdue) + real hermes cron tick / list / status under an isolated HERMES_HOME=/tmp/repro-99879 — before (origin/main): the catch-up fired and cron list showed only Last run: ... ok with no hint it was late (catch_up_occurrences counter incremented but nothing user-visible); after: cron list shows Dispatch: ⚠ catch-up after missed fire: scheduled 2026-08-31T09:00:00-07:00, ran 2026-09-01T08:20:38-07:00 (23h 20m late) and cron status shows ⚠ 1 job(s) last fired late (missed-fire catch-up): ....

Tests

  • tests/cron/test_dispatch_lateness_stamp.py — due-scan stamps catch_up/late/on_time correctly, persists to storage, skips manual triggers, overwrites stale stamps; classification boundary tests.
  • tests/hermes_cli/test_cron_dispatch_visibility.py — cron list Dispatch line rendering (loud vs quiet vs absent), cron status late-jobs callout, formatting helpers, malformed-stamp tolerance.
  • Sabotage-verified: removing the jobs.py stamp fails 4 tests; removing the CLI render line fails 2 tests.
  • Full mirror dirs green: tests/cron/ + hermes_cli cron tests — 1121 passed, 0 failed.

Related: PR #99919 tags the beyond-grace catch-up branch with an in-memory-only _dispatch_meta; this PR persists the stamp for all recurring dispatches (so on-time vs late is distinguishable cross-process) and adds the actual CLI rendering, which #99919 does not touch.

Infographic

cron-catch-up-visibility

…` / `status`

The catch-up machinery already re-ran jobs missed during gateway downtime,
but the late execution rendered as an ordinary on-time success — no
scheduled-vs-actual time, no lateness, no disposition (issue #99879, the
visibility half).

- Due-scan now persists a `last_dispatch` stamp on every recurring dispatch:
  scheduled_at, dispatched_at, lateness_seconds, and kind
  (on_time / late / catch_up, classified against the ticker tolerance and
  the schedule's catch-up grace window). Manual triggers and one-shots are
  not stamped (no scheduled instant to be late against / retired beyond
  grace).
- `hermes cron list` renders a per-job Dispatch line; late/catch-up runs
  show "⚠ catch-up after missed fire: scheduled ..., ran ... (31m late)".
- `hermes cron status` calls out jobs whose last dispatch was late or a
  catch-up, in both the built-in ticker and external provider paths.

CLI surface only — no new tools, no policy engine.

Addresses the visibility half of #99879.
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on e60a0e2 — fix(cron): surface missed-fire catch-up lateness in `hermes

⚠️ Warnings

OSV vulnerability scan · View job

6 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 4m21s vs 4m24s (-1.1%). 6 job(s) slower, 6 faster, 1 unchanged.

  • OS-specific tests / Windows-only tests: -17.0s
  • OSV scan / Scan lockfiles / osv-scan: +7.0s
  • OS-specific tests / macOS-only tests: +7.0s
  • Python tests / e2e: +4.0s
  • Python lints / ruff enforcement (blocking): -4.0s

@teknium1
teknium1 merged commit 71c4bcf into main Sep 1, 2026
36 checks passed
@teknium1
teknium1 deleted the fix/99879-cron-missed-run-visibility branch September 1, 2026 15:31
@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants