Repository navigation
Description
How do you use Sentry?
Sentry Saas (sentry.io)
Version
2.72.0 (Flask 3.1.3, Python 3.9). Same code path on master and major/3.0.
Steps to Reproduce
A common pattern: log the exception, then re-raise it.
# python repro.py <plain|log-then-raise>
import gzip, json, logging, sys, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import sentry_sdk
from flask import Flask
sessions, events = [], []
class Sink(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def do_POST(self):
body = self.rfile.read(int(self.headers["Content-Length"]))
if self.headers.get("Content-Encoding") == "gzip":
body = gzip.decompress(body)
lines = body.split(b"\n")
for i in range(1, len(lines) - 1, 2):
try:
item = json.loads(lines[i])
payload = json.loads(lines[i + 1])
except Exception:
continue
if item.get("type") == "sessions":
sessions.extend(payload["aggregates"])
if item.get("type") == "event":
values = payload["exception"]["values"]
events.append(values[-1]["mechanism"]["handled"])
self.send_response(200)
self.end_headers()
srv = HTTPServer(("127.0.0.1", 0), Sink)
threading.Thread(target=srv.serve_forever, daemon=True).start()
sentry_sdk.init(dsn="http://k@127.0.0.1:%d/1" % srv.server_port, release="demo@1.0.0")
app = Flask(__name__)
log = logging.getLogger("app")
mode = sys.argv[1]
@app.route("/")
def index():
try:
raise ValueError("db down")
except ValueError:
if mode == "log-then-raise":
log.exception("failed to load")
raise
logging.getLogger("werkzeug").disabled = True
app.logger.disabled = True
app.test_client().get("/")
sentry_sdk.get_client().session_flusher.flush()
sentry_sdk.flush()
totals = {k: sum(a.get(k, 0) for a in sessions) for k in ("exited", "errored", "crashed")}
print(f"{mode:15} events handled={events} request sessions={totals}")pip install sentry-sdk flask, then python repro.py plain and python repro.py log-then-raise.
Expected Result
Both requests fail with a 500, so both should count as crashed, and the event should keep handled: false.
Actual Result
plain events handled=[False] request sessions={'exited': 0, 'errored': 0, 'crashed': 1}
log-then-raise events handled=[True] request sessions={'exited': 0, 'errored': 1, 'crashed': 0}
With log.exception() first, the logging event (handled) goes out and updates the session to errored. The unhandled event from Flask is the same exception object, so DedupeIntegration drops it before the session update in capture_event. The crash never reaches the session or the issue.
The spec says dropped events shouldn't update the session because the developer chose to ignore them, but that isn't the case here.
Metadata
Metadata
Assignees
Projects
- StatusShow more project fieldsWaiting for: Product Owner
Activity
linear-code commentedon Oct 21, 2026
PY-3029
simpleqt commentedon Nov 1, 2026
Diagnosis + proposed direction (happy to open a draft PR once there's maintainer interest):
sentry_sdk/integrations/dedupe.pykeys_last_seenon the exception object alone, so when the same exception instance is first captured as handled (e.g.capture_exceptionin anexceptblock) and later re-raised and reported as unhandled by the crash processor, the second event is dropped as a duplicate. The unhandled (crash) report never reaches Sentry and the session exitserrored(1 error) instead ofcrashed(2).Proposed fix: key dedupe on
(exception, unhandled)whereunhandledmirrors the exact checkClient._update_session_from_eventuses (anymechanism.handled is False, AnnotatedValue-aware). Duplicates then collapse only when both identity AND handledness match, so the handled report and the later crash report both survive; true duplicates (same exception, same handledness) still collapse — no behavior change for the common path.If that direction sounds right I'll open a draft PR with tests covering both the log-then-raise pattern and the same-handledness dedupe guard.