fix(curator): floor interval_hours at 1 and warn once per bad value

interval_hours <= 0 made should_run_now() true on every idle tick,
re-running the review pass each time. Route it through the same
floor-with-default helper as the day counts (renamed _bounded_count),
and log the fallback warning once per (key, value) since the dashboard
status endpoint polls these getters.
This commit is contained in:
kshitijk4poor
2026-09-24 18:56:21 +05:30
committed by kshitij
parent 342c29250d
commit e62a47ab68
2 changed files with 43 additions and 6 deletions

View File

@@ -107,32 +107,39 @@ def is_enabled() -> bool: # default ON when no config says otherwise
def get_interval_hours() -> int:
return _config_number("interval_hours", DEFAULT_INTERVAL_HOURS, int)
# < 1 would make should_run_now() true on every idle tick (a review pass each time), so floor it the same way.
return _bounded_count("interval_hours", DEFAULT_INTERVAL_HOURS)
def get_min_idle_hours() -> float:
return _config_number("min_idle_hours", DEFAULT_MIN_IDLE_HOURS, float)
def _bounded_days(key: str, default: int) -> int:
"""*key* (a ``curator.<key>`` day count), floored at 1 like ``curator prune --days`` already
_warned_bad_values: set = set()
def _bounded_count(key: str, default: int) -> int:
"""*key* (a ``curator.<key>`` day/hour count), floored at 1 like ``curator prune --days`` already
refuses (hermes_cli/curator.py::_cmd_prune). A value < 1 collapses stale_cutoff/archive_cutoff
onto or past "now" in apply_automatic_transitions(), mass-transitioning every skill with any
past activity on the next automatic pass — unlike the manual prune path this runs unconfirmed,
so it falls back to the default instead of acting on the bad value."""
value = _config_number(key, default, int)
if value < 1:
logger.warning("curator.%s must be >= 1 (got %d); using the default of %d", key, value, default)
# Warn once per (key, bad value): the dashboard status endpoint polls these getters.
if (key, value) not in _warned_bad_values:
_warned_bad_values.add((key, value))
logger.warning("curator.%s must be >= 1 (got %d); using the default of %d", key, value, default)
return default
return value
def get_stale_after_days() -> int:
return _bounded_days("stale_after_days", DEFAULT_STALE_AFTER_DAYS)
return _bounded_count("stale_after_days", DEFAULT_STALE_AFTER_DAYS)
def get_archive_after_days() -> int:
return _bounded_days("archive_after_days", DEFAULT_ARCHIVE_AFTER_DAYS)
return _bounded_count("archive_after_days", DEFAULT_ARCHIVE_AFTER_DAYS)
def get_consolidate() -> bool: