Review Sentry Python and Django changes for bug patterns drawn from real production issues. Use when reviewing a backend diff or PR, checking Warden findings, auditing the current branch, reviewing production-error patterns, or looking for common regressions in `src/` and `tests/`.
Find bugs in Sentry backend code by checking for the patterns that cause the most real production errors.
This skill encodes patterns from 638 real production issues (393 resolved, 220 unresolved, 25 ignored) generating over 27 million error events across 65,000+ affected users. These are not theoretical risks -- they are the actual bugs that ship most often, with known fixes from resolved issues.
Review the code provided by the user, Warden, or the current branch diff. If the user does not provide a target, review the current branch diff. Start from the changed hunk or file, then read outward only as needed to confirm the behavior.
Read and Grep to trace data flow beyond the initial diff when needed. Follow function calls, callers, serializers, tasks, and ORM boundaries until the behavior is confirmed.| Confidence |
|---|
| Criteria |
|---|
| Action |
|---|
| HIGH | Traced the code path, confirmed the pattern matches a known bug class | Report with fix |
| MEDIUM | Pattern is present but context may mitigate it | Report as needs verification |
| LOW | Theoretical or mitigated elsewhere | Do not report |
Determine what you are reviewing and load the relevant reference.
| Code Type | Load Reference |
|---|---|
ORM queries, model lookups, .objects.get(), FK access | references/missing-records.md |
| Type conversions, None handling, option reads, serializer returns | references/null-and-type-errors.md |
| Data input parsing, field lengths, request bodies, decompression | references/data-validation.md |
get_or_create, save(), unique constraints, integer overflow | references/database-integrity.md |
| Integration webhooks, external API calls, SentryApp hooks | references/integration-errors.md |
| Dict iteration, shared state, concurrent access | references/concurrency-bugs.md |
| Snuba queries, metric subscriptions, search filters | references/query-validation.md |
| Redirect URLs, URL construction, routing | references/url-safety.md |
If the code spans multiple categories, load all relevant references.
These are ordered by combined frequency and impact from real production data.
Alert and metric subscriptions referencing tags or functions that do not exist in the target dataset. These fire continuously once created.
Red flags:
SubscriptionData using user-provided query strings without validationtransaction.duration in p95/p99 functions on the metrics dataset (it is a string type there)customerType) as filter dimensions without checking they existresolve_apdex_function without verifying the dataset supports threshold parametersSafe patterns:
_create_in_snuba calls with try/except SubscriptionError and mark subscription as invalidIncompatibleMetricsQuery checks before building metric subscription queriesCode calls .get() on a Django model assuming the record exists, but it has been deleted, merged, or never created.
Red flags:
Model.objects.get(id=some_id) without try/except for DoesNotExistDetector.objects.get(id=detector_id) in workflow engine without handling deletionEnvironment.objects.get(name=env_name) in monitor/cron consumersSubscription.objects.get(id=sub_id) in billing tasksGroup.objects.get() with IDs from Snuba query results (groups may be deleted/merged).get() failsSafe patterns:
Model.objects.filter(...).first() with a None checkDoesNotExist that returns a graceful fallback (404, skip, log).exists() check before .get()DoesNotExist, 400 for validation errors. Never suggest returning 500 intentionally.Not a bug — do not flag:
.get() enforcing a deployment precondition (e.g., "default org must exist in single-org mode") should crash — a 500 signals misconfiguration, not a code defect.OrganizationEndpoint resolves the org), don't flag .get() on related records unless there's a genuine race or deletion window. Read the endpoint's parent class before reporting.get_default(), settings-based lookups) is expected to fail hard if the config is wrong.InvalidSearchQuery from user-provided or subscription-stored query filters referencing invalid values, deleted issues, or unresolved tags.
Red flags:
by_qualified_short_id_bulk() called with short IDs from stored subscriptions that reference deleted/renamed projects_issue_filter_converter that calls Group.objects.get() on user-provided issue short IDsresolve_tag_key() called without checking the tag exists in the target datasetquery strings from alert rule subscriptions to SnubaSafe patterns:
by_qualified_short_id_bulk() -- handle Group.DoesNotExist_issue_filter_converter in try/except and return empty results for invalid filtersValueError from insufficient input validation: unpacking errors, invalid enum values, missing expected objects, and Pydantic/DRF validation failures.
Red flags:
SentryAppInstallation.objects.get() in action builders assuming exactly 1 result existsAlertRuleWorkflow lookups by ID without handling DoesNotExista, b, c = value.split(":")) on strings that may have fewer separatorsValueError (e.g., DetectorPriorityLevel(value))Safe patterns:
.get(): use .filter() and check .count()ValueError or validate length firsttry: EnumClass(value) except ValueError: for user-facing enum conversionsValueError and validation failures.Wrong types passed to functions: iterating over non-iterables, invalid dict keys, None where object expected.
Red flags:
orjson.dumps(payload) where payload dict may have non-string keyslist(setting) where setting could be an int (from project.get_option())result["key"] = value where result could be NoneSafe patterns:
isinstance(value, (list, str)) before list(value)if result is not None:ApiError from internal Sentry API calls between services, often caused by stale subscription parameters.
Red flags:
api.client.get() calls in metric alert chart rendering without handling 400 responsesfetch_metric_alert_events_timeseries() in incidents/charts.py without ApiError handlingSafe patterns:
ApiError and return graceful fallbackIntegrityError and DataError from integer overflow, foreign key violations, and unique constraint violations.
Red flags:
times_seen counter without bounding (integer overflow at ~2.1 billion)MonitorCheckIn records without handling FK constraints from MonitorIncidentUPDATE on GroupOpenPeriod date ranges where lower bound can exceed upper boundsave() on models with unique constraints without handling IntegrityErrorSafe patterns:
min(value, 2_147_483_647) for 32-bit int columnsCASCADE or handle FK constraints before bulk deletionIntegrityError with fallback to get() or update_or_create()JSONDecodeError and ZstdError from parsing external data or stored compressed data.
Red flags:
json.loads(response.body) without catching JSONDecodeErrorzstd.decompress(data) without handling corrupt frame descriptorsSafe patterns:
content-type header before parsingAccessing dictionary keys or HTTP headers without existence check.
Red flags:
request.META["HTTP_X_GITLAB_TOKEN"] in webhook handlers (header may be absent)request.META["HTTP_X_EVENT_KEY"] in Bitbucket webhook handlerHANDLERS[event_type] without checking the event type is registeredSafe patterns:
request.META.get("HTTP_X_GITLAB_TOKEN") with None checkHANDLERS.get(event_type) with fallback for unknown event typesDictionary mutation during iteration, shared mutable state, and unimplemented code paths.
Red flags:
for key in self._dict: while another thread modifies it (RuntimeError)KafkaPublisher dict that grows unboundeddict.pop() or dict[key] = value on a dict being iterated in another threadNotImplementedError handlers for new search expression typesSafe patterns:
dict.copy() before iterationthreading.Lock for shared mutable statelist(dict.keys()) for safe iteration when mutation is neededAfter checking all known patterns above, reason about the changed code itself:
else / default cases)?Only report if you can trace a specific input that triggers the bug. Do not report theoretical concerns.
Not a bug — do not flag:
assert statements enforcing infrastructure invariants — Sentry does not run with python -O, so assertions are always active. Crashing on a violated invariant is intentional.If no checks produced a potential finding, stop and report zero findings. Do not invent issues to fill the report. An empty result is the correct output when the code has no bugs matching these patterns.
Each code location should be reported once under the most specific matching pattern. Do not flag the same line under multiple checks.
For each finding, provide the evidence the review harness needs:
Fix suggestions must include actual code. Never suggest a comment or docstring as a fix.
When suggesting fixes in API endpoints, use appropriate HTTP status codes (404 for not found, 400 for bad input, 409 for conflicts). Never suggest returning 500 intentionally.
Do not prescribe your own output format — the review harness controls the response structure.