Skip to content

fix: normalize cron repeat updates - #18416

Closed
hanzckernel wants to merge 1 commit into
NousResearch:mainfrom
hanzckernel:fix/cron-repeat-update-normalization
Closed

fix: normalize cron repeat updates#18416
hanzckernel wants to merge 1 commit into
NousResearch:mainfrom
hanzckernel:fix/cron-repeat-update-normalization

Conversation

@hanzckernel

@hanzckernel hanzckernel commented May 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #15582

Related:

Cron repeat updates now stay in the canonical scheduler shape even when callers use Web UI/API-style scalar or null payloads.

Changes

  • Normalize repeat updates into { times, completed } instead of persisting raw scalar values.
  • Preserve the existing completed-run count when changing or clearing the repeat limit.
  • Allow repeat: null on update to clear a repeat limit.
  • Reject invalid API update repeat values (0, negative numbers, strings, floats, booleans).
  • Normalize legacy scalar repeat values on get_job, list_jobs, and mark_job_run, so already-corrupted persisted jobs do not wait until the next run to fail.
  • Add regression coverage across cron storage, API update validation, and the cronjob tool list path.

Why

The Web UI edit surface sends repeat as a scalar integer or null, while cron jobs are stored with repeat metadata as an object. Without backend normalization, a PATCH such as { "repeat": 3 } can persist repeat: 3; the next run then crashes when repeat accounting expects .get() / completed.

Earlier fixes covered the core scalar-to-dict path, but not all edge cases seen from the Web UI/API boundary. This PR is intended to supersede those variants with the full persisted-state contract covered in tests.

Verification

Passed:

./scripts/run_tests.sh tests/cron/test_jobs.py tests/gateway/test_api_server_jobs.py tests/tools/test_cronjob_tools.py
./scripts/run_tests.sh tests/cron tests/hermes_cli/test_web_server.py
python -m py_compile cron/jobs.py gateway/platforms/api_server.py
git diff --check

Results:

  • 144 passed, 42 warnings for targeted cron/API/tool tests
  • 431 passed for broader cron + web-server coverage
  • py_compile passed
  • diff check passed

Known:

  • gh pr checks currently reports no checks for this branch, so local verification above is the available gate.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels May 1, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracing the scalar-repeat mismatch; the premise is still valid on current main: gateway/platforms/api_server.py:3672 forwards PATCH values to cron/jobs.py:update_job, which directly merges updates at cron/jobs.py:1220.

Problems

  • Current one-shot execution calls claim_dispatch() before mark_job_run() (cron/scheduler.py:3372). claim_dispatch() reads raw persisted repeat and calls .get() at cron/jobs.py:1494-1497, so a legacy scalar one-shot can still fail before the proposed mark-time repair runs. _get_due_jobs_locked() has the same raw-map assumption at cron/jobs.py:1814-1817.
  • The diff also adds per-job profile storage (cron/jobs.py:671-692, :798-853) without a scheduler consumer or API exposure. That is unrelated to the stated fix and leaves dead persisted state.

Suggested changes

  • Salvage repeat normalization onto current update_job, then cover all raw scheduler consumers while preserving the current pre-claim one-shot invariant.
  • Add an API-to-storage-to-one-shot-claim regression, and drop the unrelated profile delta.

This is an automated hermes-sweeper review.

Comment thread cron/jobs.py Outdated
@@ -848,6 +942,18 @@ def update_job(job_id: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]
else:
updates["workdir"] = _normalize_workdir(_wd)

if "repeat" in updates:
updates["repeat"] = _normalize_repeat_state(updates["repeat"], job.get("repeat"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This protects new updates, but current main's claim_dispatch() reads the raw stored value and calls .get() before mark_job_run() executes (cron/jobs.py:1494-1497). Extend legacy repair to that pre-run path and add a scalar one-shot regression.

Comment thread cron/jobs.py Outdated
@@ -703,6 +795,7 @@ def create_job(
normalized_toolsets = [str(t).strip() for t in enabled_toolsets if str(t).strip()] if enabled_toolsets else None
normalized_toolsets = normalized_toolsets or None
normalized_workdir = _normalize_workdir(workdir)
normalized_profile = _normalize_profile(profile)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This profile plumbing is outside the repeat fix. The PR-head scheduler has no job-profile consumer and the API allowlist does not expose profile; please drop it from this PR or land it separately with an execution-path implementation and tests.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@hanzckernel
hanzckernel force-pushed the fix/cron-repeat-update-normalization branch from c98b940 to 999668d Compare July 13, 2026 01:08
@hanzckernel
hanzckernel deleted the fix/cron-repeat-update-normalization branch July 20, 2026 00:05
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 comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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.

API job repeat updates corrupt cron repeat state

3 participants