From 0d214e6a6b3586b71ec83e7ff2c509bc9a74e33f Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Sat, 3 Oct 2026 01:34:23 +0200 Subject: [PATCH 1/2] fix(crons): Return `None` from `capture_checkin` when nothing is sent Return None when capture_event does not send the check-in, because there is no active client or before_send drops the event. Previously the generated or passed-in id came back either way, so a caller that hands the id to another process could not tell that the check-in never reached Sentry. Fixes GH-7845 --- sentry_sdk/crons/api.py | 5 +++-- tests/test_crons.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/crons/api.py b/sentry_sdk/crons/api.py index 6ea3e36b6d..1d81582db6 100644 --- a/sentry_sdk/crons/api.py +++ b/sentry_sdk/crons/api.py @@ -42,7 +42,7 @@ def capture_checkin( status: "Optional[str]" = None, duration: "Optional[float]" = None, monitor_config: "Optional[MonitorConfig]" = None, -) -> str: +) -> "Optional[str]": check_in_event = _create_check_in_event( monitor_slug=monitor_slug, check_in_id=check_in_id, @@ -51,7 +51,8 @@ def capture_checkin( monitor_config=monitor_config, ) - sentry_sdk.capture_event(check_in_event) + if sentry_sdk.capture_event(check_in_event) is None: + return None logger.debug( f"[Crons] Captured check-in ({check_in_event.get('check_in_id')}): {check_in_event.get('monitor_slug')} -> {check_in_event.get('status')}" diff --git a/tests/test_crons.py b/tests/test_crons.py index 30031db6e1..8a7ee32ec6 100644 --- a/tests/test_crons.py +++ b/tests/test_crons.py @@ -321,7 +321,17 @@ def test_capture_checkin_sdk_not_initialized(): status=None, duration=None, ) - assert check_in_id == "112233" + assert check_in_id is None + + +def test_capture_checkin_dropped_by_before_send(sentry_init, capture_envelopes): + sentry_init(before_send=lambda event, hint: None) + envelopes = capture_envelopes() + + check_in_id = capture_checkin(monitor_slug="abc123", status="in_progress") + + assert check_in_id is None + assert envelopes == [] def test_scope_data_in_checkin(sentry_init, capture_envelopes): From 99ec2d842f06630d0ab68caaab11ca3f421ba2bb Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Mon, 5 Oct 2026 18:54:40 +0200 Subject: [PATCH 2/2] test(crons): Cover the monitor exit check-in after a dropped enter Test written by ericapisani in review. --- tests/test_crons.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_crons.py b/tests/test_crons.py index 8a7ee32ec6..035b0df801 100644 --- a/tests/test_crons.py +++ b/tests/test_crons.py @@ -334,6 +334,31 @@ def test_capture_checkin_dropped_by_before_send(sentry_init, capture_envelopes): assert envelopes == [] +def test_context_monitor_exit_successfully_sends_checkin_when_enter_checkin_dropped( + sentry_init, capture_envelopes +): + sentry_init( + before_send=lambda event, hint: ( + None if event.get("status") == "in_progress" else event + ) + ) + envelopes = capture_envelopes() + + monitor = sentry_sdk.monitor(monitor_slug="abc123") + with monitor: + # Confirms that the check in was dropped in the `__enter__` + assert monitor.check_in_id is None + + (envelope,) = envelopes + check_in = envelope.items[0].payload.json + + # Confirms that the second check in in `__exit__` works correctly + assert check_in["monitor_slug"] == "abc123" + assert check_in["check_in_id"] + assert check_in["status"] == "ok" + assert check_in["duration"] > 0 + + def test_scope_data_in_checkin(sentry_init, capture_envelopes): sentry_init() envelopes = capture_envelopes()