fix(gateway): make cron ticker errors visible at default log level - #32616
fix(gateway): make cron ticker errors visible at default log level#32616briandevans wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Improves visibility and handling of errors occurring inside the gateway cron ticker loop so operators can see failures at default log levels and fatal errors don’t silently kill the ticker thread.
Changes:
- Log regular cron tick exceptions at WARNING with tracebacks.
- Log and re-raise BaseException-derived failures so the ticker thread exits loudly/cleanly.
- Add regression tests covering both behaviors (Exception vs BaseException).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| gateway/run.py | Adjusts cron ticker exception handling to log at higher severity with tracebacks and re-raise fatal errors. |
| tests/gateway/test_cron_ticker_error_visibility.py | Adds tests asserting correct log levels/tracebacks and re-raise behavior for fatal exceptions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import pytest | ||
|
|
||
|
|
| except BaseException: | ||
| # `except Exception` does not catch SystemExit, KeyboardInterrupt, | ||
| # or BaseExceptionGroup — log a traceback first, then re-raise so | ||
| # the thread exits as Python intends rather than dying silently. | ||
| logger.error( | ||
| "Cron ticker fatal error; thread will exit", exc_info=True | ||
| ) | ||
| raise |
|
@copilot All findings addressed in 697fbb288:
All three existing tests still pass. |
697fbb2 to
5892805
Compare
The inner exception handler around `cron_tick(...)` in `_start_cron_ticker` swallowed errors at DEBUG and only caught `Exception`. Two consequences: 1. Tick failures were invisible at the default INFO log level, so cron jobs could silently stop firing for hours with `hermes cron status` still reporting healthy. 2. Any BaseException-subclass error (SystemExit, KeyboardInterrupt, BaseExceptionGroup, C-extension failures) killed the ticker thread with no log line at all. Escalate the Exception path to WARNING with `exc_info=True` so the traceback surfaces at the default log level, and add a BaseException arm that logs at ERROR and re-raises so the thread exits as Python intends rather than dying silently. Watchdog/health-check fixes (bug NousResearch#3 in the upstream issue) are out of scope here — they overlap with the in-flight NousResearch#26734. Fixes NousResearch#32612
Address Copilot review on NousResearch#32616: - gateway/run.py: narrow `except BaseException` to `(SystemExit, KeyboardInterrupt, BaseExceptionGroup)` so we don't intercept GeneratorExit or other interpreter-shutdown signals while still capturing the cases the docstring describes. - tests/gateway/test_cron_ticker_error_visibility.py: drop unused `pytest` and `unittest.mock.patch` imports. All three existing tests continue to pass; the change is type-narrowing on the exception filter, not a behavior change on the cases the tests exercise (SystemExit + KeyboardInterrupt are still re-raised).
5892805 to
2eaac64
Compare
|
I found one issue that looks worth fixing before merge. Unreachable
except Exception:
logger.warning("Cron tick error", exc_info=True)
except (SystemExit, KeyboardInterrupt, BaseExceptionGroup):
logger.error("Cron ticker fatal error; thread will exit", exc_info=True)
raise
Why it matters: If Suggested fix: Remove # Option A: accept that Exception groups are non-fatal for the ticker
except Exception:
logger.warning("Cron tick error", exc_info=True)
except (SystemExit, KeyboardInterrupt):
logger.error("Cron ticker fatal error; thread will exit", exc_info=True)
raise
# Option B: re-raise exception groups as fatal
except BaseExceptionGroup:
logger.error("Cron ticker fatal error; thread will exit", exc_info=True)
raise
except Exception:
logger.warning("Cron tick error", exc_info=True)
except (SystemExit, KeyboardInterrupt):
logger.error("Cron ticker fatal error; thread will exit", exc_info=True)
raiseOption A is simpler and correct for a ticker loop — The rest of the PR (DEBUG→WARNING upgrade, exc_info=True, test coverage) looks solid. |
|
Closing to focus the queue on security/file-safety work where civilian merges are landing. Happy to reopen if maintainers want this picked up. |
What does this PR do?
The inner
try/exceptaroundcron_tick(...)inside_start_cron_tickerlogged failures at DEBUG and only matchedException. Two consequences from #32612:hermes cron statusstill reporting healthy.BaseExceptioncatch. ASystemExit/KeyboardInterrupt/BaseExceptionGroup(or anything thrown by a C extension) killed the ticker thread with zero log output.This PR addresses the Immediate (low risk) part of the reporter's fix list:
Exceptionarm toWARNINGand switch toexc_info=Trueso the traceback surfaces at the default log level.BaseExceptionarm that logs atERRORwith traceback, then re-raises so the thread exits as Python intends rather than dying silently.The watchdog /
hermes cron statusthread-liveness fix (the issue's bug #3) is intentionally out of scope here — it overlaps with the in-flight #26734 (cron ticker watchdog) and is a much larger structural change. Splitting the two keeps this PR a 12-line surgical fix that can land independently.Related Issue
Fixes #32612
Type of Change
Changes Made
gateway/run.py— split theexcept Exceptionblock in_start_cron_tickerinto a WARNING-levelExceptionarm (withexc_info) and an ERROR-levelBaseExceptionarm that re-raises. Both keep the existing single-call-site structure.tests/gateway/test_cron_ticker_error_visibility.py— three regression tests driving_start_cron_tickerwith a mockedcron.scheduler.tick: one each forException(logged at WARNING with traceback),SystemExit(ERROR + re-raised), andKeyboardInterrupt(ERROR + re-raised).How to Test
Expected: 134 passed. All 3 new tests fail on
main(confirmed viagit stash+ rerun); all 3 pass with this change applied.Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) — N/A (in-line comments cover the WHY)cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/ARelated / Positioning
cron.scheduler.tick. Different module, different concern; no overlap.For New Skills
N/A.
Screenshots / Logs
Before (default INFO level, ticker dies silently):
After this change (default INFO level, same failure):