langchain-ai/deepagents · error · CronJobError
cron retention window cannot be negative
Error message
cron retention window cannot be negative
What it means
CronJobStore.prune_completed deletes disabled, completed jobs older than a retention window. A negative retain_for timedelta is rejected up front with CronJobError because it would make the cutoff in the future and silently delete jobs that are not actually expired.
Source
Thrown at libs/talon/deepagents_talon/cron/jobs.py:601
*,
retain_for: timedelta,
now: datetime | None = None,
) -> list[CronJob]:
"""Delete completed jobs older than the retention window.
Args:
retain_for: Duration to keep disabled jobs after completion.
now: Current timestamp override for deterministic tests.
Returns:
Removed job records.
Raises:
CronJobError: If `retain_for` is negative.
"""
if retain_for < timedelta(0):
msg = "cron retention window cannot be negative"
raise CronJobError(msg)
cutoff = _coerce_utc(now) - retain_for
kept: list[CronJob] = []
removed: list[CronJob] = []
for job in self.list_jobs():
reference = job.last_run_at or job.created_at
if not job.enabled and job.next_run_at is None and reference <= cutoff:
removed.append(job)
else:
kept.append(job)
if removed:
self._write_jobs(kept)
return removed
def _read_jobs(self) -> list[CronJob]:
self._ensure_store()
if not self.path.exists():
return []View on GitHub (pinned to a1af029e6e)
Solutions
- Pass a non-negative timedelta, e.g. timedelta(days=7) or timedelta(0).
- Clamp computed windows: retain_for = max(computed, timedelta(0)) before calling.
- Fix the config/source value that produced the negative duration and re-run cleanup.
Example fix
// before removed = store.prune_completed(retain_for=timedelta(seconds=expiry_ts - now_ts)) // after window = timedelta(seconds=max(0, expiry_ts - now_ts)) removed = store.prune_completed(retain_for=window)
Defensive patterns
Strategy: validation
Validate before calling
if retain_for < timedelta(0):
raise ValueError("retain_for must be >= 0") Type guard
def is_non_negative(delta: timedelta) -> bool:
return delta >= timedelta(0) Try / catch
try:
removed = store.prune_completed(retain_for=retain_for)
except CronJobError as exc:
logger.error("bad retention window: %s", exc)
removed = store.prune_completed(retain_for=timedelta(0)) Prevention
- Clamp derived windows with max(computed, timedelta(0)).
- Validate retention config at startup, before any cleanup runs.
- Watch subtraction order when computing windows from timestamps.
When it happens
Trigger: Calling store.prune_completed(retain_for=timedelta(days=-1)) or computing retain_for from a configuration value that can go negative (e.g., expiry_ts - now where expiry already passed).
Common situations: Config-driven retention where an env/config value is parsed with the wrong sign; deriving the window by subtracting timestamps in the wrong order; a caller passing timedelta(0) is fine, but a typo like timedelta(hours=-1) from arithmetic is common.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- thread_id must be non-empty
- offset_seconds must be >= 0, got {offset_seconds!r}
- sampling_rate must be > 0, got {sampling_rate!r}
- duration_seconds must be > 0, got {duration_seconds!r}
- cron schedules must be at least 1 minute
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/9bdedd95574224e5.
Report an issue: GitHub.