Skip to content

capture_checkin returns a check-in id even when the check-in was not sent #7845

Description

@HardMax71

How do you use Sentry?

Sentry Saas (sentry.io)

Version

2.71.0 (same on 2.48.0)

Steps to Reproduce

import sentry_sdk
from sentry_sdk.crons import capture_checkin
from sentry_sdk.transport import Transport

# 1. no active client
print(sentry_sdk.capture_event({"message": "x"}))                     # None
print(capture_checkin(monitor_slug="nightly", status="in_progress"))  # '19fe1f76b846410381dd7937594d1ceb'

# 2. check-in dropped by before_send
sent = []

class RecordingTransport(Transport):
    def capture_envelope(self, envelope):
        sent.append(envelope)

sentry_sdk.init(dsn="https://[email protected]/1", transport=RecordingTransport,
                before_send=lambda event, hint: None)
print(capture_checkin(monitor_slug="nightly", status="in_progress"))  # '396229524fde41ce9cf4755cae73da0c'
sentry_sdk.flush()
print(len(sent))                                                      # 0

Expected Result

The caller can tell that the check-in didn't go out, the same way capture_event returns None when the event is dropped. For example, capture_checkin returns None in that case.

Actual Result

capture_checkin ignores the return value of capture_event and always returns the generated (or passed-in) check_in_id.

This matters when the check-in is opened in one process and closed in another. We run scheduled Dramatiq jobs: the scheduler opens the in_progress check-in and passes the id to the worker in the message, and the worker opens its own check-in only when no id arrives. When the scheduler has no active client, the worker still gets an id, skips opening its own, and closes a check-in that never reached Sentry. We now check sentry_sdk.get_client().is_active() before calling capture_checkin, but that doesn't cover a check-in dropped in before_send. The Celery Beat integration hands the id over the same way (sentry-monitor-check-in-id).

A possible change: return None from capture_checkin when capture_event returns None, so the return type becomes Optional[str]. monitor and the Celery Beat integration keep working, because capture_checkin(check_in_id=None) generates a fresh id. One existing test would change: test_capture_checkin_sdk_not_initialized asserts that the passed-in id comes back when the SDK is not initialized. I have this change with tests ready locally (tests/test_crons.py and the Celery Beat crons tests pass, mypy is clean) and can open a PR if the approach is OK with you.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions