关于 sentry-backend-bugs
面向 Sentry 后端代码审查的技能,依据实际生产问题归纳常见缺陷模式。它要求追踪代码路径,并仅报告高或中置信度的问题及修复建议。
主要功能
- 01按缺陷类别检查代码
- 02追踪调用与数据流
- 03覆盖常见运行时和数据问题
- 04仅报告中高置信度发现
- 05提供安全处理模式
适用场景
- 01审查当前分支的后端改动
- 02检查 ORM 查询的缺失记录风险
- 03排查输入解析与数据库完整性问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ getsentry/ sentry --skill sentry-backend-bugs安装后在 Claude Code 中输入/sentry-backend-bugs确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/concurrency-bugs.mddata-validation.mddatabase-integrity.mdintegration-errors.mdmissing-records.mdnull-and-type-errors.mdquery-validation.mdurl-safety.md详细介绍
Sentry 后端 Bug 模式审查
通过检查最常导致真实生产错误的模式,发现 Sentry 后端代码中的 Bug。
此技能编码了来自 638 个真实生产问题的模式(393 个已解决、220 个未解决、25 个已忽略),这些问题在 65,000+ 受影响用户中生成了超过 2700 万个错误事件。这些不是理论上的风险——它们是实际最常发布的 Bug,并带有来自已解决问题的已知修复方案。
范围
审查用户、Warden 或当前分支 diff 提供的代码。如果用户未提供目标,则审查当前分支 diff。从更改的 hunk 或文件开始,然后仅在需要确认行为时向外读取。
- 根据以下模式检查分析更改的代码。
- 在需要时,使用
Read和Grep追踪初始 diff 之外的数据流。沿着函数调用、调用方、序列化器、任务和 ORM 边界追踪,直到行为得到确认。 - 仅报告 HIGH 和 MEDIUM 置信度的发现。
| 置信度 | 标准 | 操作 |
|---|---|---|
| HIGH | 已追踪代码路径,确认该模式与已知 Bug 类别匹配 | 报告并提供修复 |
| MEDIUM | 模式存在,但上下文可能减轻其影响 | 报告为需要验证 |
| LOW | 理论上的,或在其他地方已减轻 | 不报告 |
步骤 1:对代码进行分类
确定你正在审查的内容,并加载相关参考。
| 代码类型 | 加载参考 |
|---|---|
ORM 查询、模型查找、.objects.get()、FK 访问 | references/missing-records.md |
| 类型转换、None 处理、选项读取、序列化器返回值 | references/null-and-type-errors.md |
| 数据输入解析、字段长度、请求体、解压缩 | references/data-validation.md |
get_or_create、save()、唯一约束、整数溢出 | references/database-integrity.md |
| 集成 webhook、外部 API 调用、SentryApp 钩子 | references/integration-errors.md |
| 字典迭代、共享状态、并发访问 | references/concurrency-bugs.md |
| Snuba 查询、指标订阅、搜索过滤器 | references/query-validation.md |
| 重定向 URL、URL 构造、路由 | references/url-safety.md |
如果代码跨越多个类别,请加载所有相关参考。
步骤 2:检查主要 Bug 模式
以下按真实生产数据中的综合频率和影响排序。
检查 1:指标订阅查询错误——113 个问题,3,035,640 个事件
告警和指标订阅引用了目标数据集中不存在的标签或函数。这些订阅一旦创建就会持续触发。
危险信号:
- 使用
SubscriptionData创建 Snuba 订阅时,使用用户提供的查询字符串而未进行验证 - 在指标数据集的 p95/p99 函数中引用
transaction.duration(在该数据集中它是字符串类型) - 将自定义标签名(例如
customerType)用作过滤维度,但未检查它们是否存在 - 在未验证数据集是否支持阈值参数的情况下调用
resolve_apdex_function
安全模式:
- 在创建订阅之前,根据数据集 schema 验证查询字段
- 用 try/except
SubscriptionError包裹_create_in_snuba调用,并将订阅标记为无效 - 在构建指标订阅查询之前,使用
IncompatibleMetricsQuery检查
检查 2:记录缺失 / 陈旧引用 -- 81 个问题,1,403,592 个事件
代码在 Django 模型上调用 .get(),假设记录存在,但该记录已被删除、合并或从未创建。
危险信号:
Model.objects.get(id=some_id)没有针对DoesNotExist的 try/except- 工作流引擎中的
Detector.objects.get(id=detector_id)未处理删除情况 - 监控/cron 消费者中的
Environment.objects.get(name=env_name) - 计费任务中的
Subscription.objects.get(id=sub_id) - 使用来自 Snuba 查询结果的 ID 调用
Group.objects.get()(分组可能已被删除/合并) - 链式查找中第二个
.get()失败
安全模式:
Model.objects.filter(...).first()配合 None 检查- try/except
DoesNotExist并返回优雅的回退(404、跳过、记录日志) - 在
.get()之前用 Queryset 的.exists()检查 - 在 API 端点中:对
DoesNotExist返回 404,对验证错误返回 400。绝不要建议故意返回 500。
不是 bug——不要标记:
- 基础设施不变量:
.get()强制执行部署前置条件(例如,“单组织模式下默认组织必须存在”)应当崩溃——500 表示配置错误,而非代码缺陷。 - 已由父类验证:如果端点基类验证了对象(例如,
OrganizationEndpoint解析组织),不要标记对相关记录的.get(),除非存在真正的竞态或删除窗口。在报告前阅读端点的父类。 - 配置查找:加载必需配置对象的代码(
get_default()、基于设置的查找)在配置错误时预期会硬失败。
检查 3:搜索查询验证 -- 57 个问题,2,001,330 个事件
来自用户提供或订阅存储的查询过滤器引用了无效值、已删除的问题或未解析的标签时出现 InvalidSearchQuery。
危险信号:
by_qualified_short_id_bulk()被调用时传入来自存储订阅的短 ID,而这些订阅引用了已删除/重命名的项目_issue_filter_converter对用户提供的问题短 ID 调用Group.objects.get()resolve_tag_key()被调用时未检查标签是否存在于目标数据集- 将来自告警规则订阅的未验证
query字符串传递给 Snuba
安全模式:
- 在传递给
by_qualified_short_id_bulk()之前验证短 ID——处理Group.DoesNotExist - 将
_issue_filter_converter包裹在 try/except 中,对无效过滤器返回空结果 - 根据数据集 schema 预先验证标签是否存在
检查 4:值验证错误 -- 45 个问题,1,000,624 个事件
输入验证不足导致的 ValueError:解包错误、无效枚举值、缺少预期对象,以及 Pydantic/DRF 验证失败。
危险信号:
- 在操作构建器中调用
SentryAppInstallation.objects.get(),假设恰好存在 1 个结果 - 按 ID 查找
AlertRuleWorkflow而未处理DoesNotExist - 对可能分隔符较少的字符串进行元组解包(
a, b, c = value.split(":")) - 整数枚举查找未捕获
ValueError(例如DetectorPriorityLevel(value))
安全模式:
- 在
.get()之前验证预期数量:使用.filter()并检查.count() - 将元组解包包裹在 try/except
ValueError中,或先验证长度 - 对面向用户的枚举转换使用
try: EnumClass(value) except ValueError: - 在 API 端点中:对
ValueError和验证失败返回 400。
检查 5:类型错误 -- 28 个问题,740,106 个事件
传给函数的类型错误:迭代不可迭代对象、无效的 dict 键、本应为对象的地方传入了 None。
危险信号:
orjson.dumps(payload),其中 payload dict 可能包含非字符串键list(setting),其中setting可能是 int(来自project.get_option())result["key"] = value,其中result可能为 None- 迭代来自 DB/config 的值,该值可能是 int 或 None 而不是 list
安全模式:
- 迭代前进行类型检查:在
list(value)之前检查isinstance(value, (list, str)) - JSON 序列化前验证 dict 键:确保所有键都是字符串
- 下标赋值前进行 None 防护:
if result is not None: - 带类型检查的防御性选项读取
- 在 API 端点中:对用户输入的类型不匹配返回 400。
检查 6:内部 API 请求错误 -- 71 个问题,829,753 个事件
服务之间内部 Sentry API 调用产生的 ApiError,通常由过期的订阅参数引起。
危险信号:
- 指标告警图表渲染中的
api.client.get()调用未处理 400 响应 - 转发过期订阅查询的内部 API 调用(引用了已移除的标签/指标)
incidents/charts.py中的fetch_metric_alert_events_timeseries()未处理 ApiError
安全模式:
- 用 try/except
ApiError包装内部 API 调用并返回优雅降级 - 在发起内部 API 请求前验证查询参数
- 在图表/可视化代码中显式处理 400/500 响应
检查 7:数据库约束冲突 -- 22 个问题,2,962,198 个事件
由整数溢出、外键冲突和唯一约束冲突引起的 IntegrityError 和 DataError。
危险信号:
- 递增
times_seen计数器时未做边界限制(约 21 亿时整数溢出) - 删除
MonitorCheckIn记录时未处理来自MonitorIncident的 FK 约束 - 对
GroupOpenPeriod日期范围执行UPDATE,其中下界可能超过上界 - 对具有唯一约束的模型调用
save()时未处理IntegrityError
安全模式:
- 更新前对整数字段设上限:对 32 位 int 列使用
min(value, 2_147_483_647) - 批量删除前使用
CASCADE或处理 FK 约束 - 保存前验证日期范围边界
- try/except
IntegrityError并回退到get()或update_or_create()
检查 8:数据解析与反序列化 -- 19 个问题,272,812 个事件
解析外部数据或存储的压缩数据时产生的 JSONDecodeError 和 ZstdError。
危险信号:
json.loads(response.body)未捕获JSONDecodeErrorzstd.decompress(data)未处理损坏的帧描述符- 解析可能被截断的 VSTS/Azure DevOps webhook 请求体
- 假定 API 响应始终是有效 JSON(可能是 HTML 错误页面)
安全模式:
- 始终用 try/except 包装 JSON/压缩操作并提供优雅降级
- 解析前检查
content-type头 - 解析请求体前验证响应状态
检查 9:缺少键访问(KeyError) -- 25 个问题,155,020 个事件
在未检查是否存在的情况下访问字典键或 HTTP 头。
危险信号:
- webhook 处理器中的
request.META["HTTP_X_GITLAB_TOKEN"](头可能不存在) - Bitbucket webhook 处理器中的
request.META["HTTP_X_EVENT_KEY"] - 使用
HANDLERS[event_type]进行 dict 键查找时未检查事件类型是否已注册 - 从头值进行元组解包时使用可变长度分割
安全模式:
- 使用
request.META.get("HTTP_X_GITLAB_TOKEN")并进行 None 检查 - 使用
HANDLERS.get(event_type)并为未知事件类型提供回退 - 在处理前于 webhook 处理器顶部验证必需的头
检查 10:并发与运行时 Bug —— 23 个问题,38,443 个事件
迭代期间修改字典、共享可变状态,以及未实现的代码路径。
危险信号:
for key in self._dict:而另一个线程在修改它(RuntimeError)- 向不断增长、无上限的共享
KafkaPublisher字典发布 - 对正在被另一个线程迭代的字典执行
dict.pop()或dict[key] = value - 新的搜索表达式类型缺少
NotImplementedError处理
安全模式:
- 迭代前先
dict.copy() - 对共享可变状态使用
threading.Lock - 在启用功能前实现所有代码路径
- 需要在迭代时修改,使用
list(dict.keys())进行安全迭代
检查 11:逻辑正确性 —— 并非基于模式
在检查上述所有已知模式后,对变更代码本身进行推理:
- 每条代码路径是否都返回正确的类型?
- 条件语句的所有分支是否都已处理(尤其是
else/ 默认情况)? - 是否有任何输入(None、空列表、0、空字符串)会导致意外行为?
- 循环、切片或范围检查中是否存在差一错误?
- 如果此代码并发运行,共享状态是否受到保护?
只有当你能够追踪到触发该 bug 的具体输入时才报告。不要报告理论上的顾虑。
不是 bug —— 不要标记:
- 用于强制基础设施不变量的
assert语句 —— Sentry 不会以python -O运行,因此断言始终处于激活状态。在不变量被违反时崩溃是有意为之。 - 推测性的输入顾虑(例如“这个 URL 可能太长”、“这个 header 可能格式错误”),除非你能证明该输入确实未经验证就到达代码路径。在报告之前检查是否存在现有验证(host 检查、schema 验证、DRF 序列化器)。
如果没有任何检查产生潜在发现,请停止并报告零发现。不要为了填充报告而捏造问题。当代码中没有匹配这些模式的 bug 时,空结果就是正确的输出。
每个代码位置应在最具体的匹配模式下报告一次。不要在多个检查下标记同一行。
步骤 3:报告发现
对于每个发现,提供评审框架所需的证据:
- 精确位置
- 严重程度和置信度
- 具体的触发输入或状态
- 根本原因和后果
- 可用的匹配生产先例
- 具体的代码修复,当框架支持时最好以 unified diff 形式提供
修复建议必须包含实际代码。绝不要建议把注释或 docstring 作为修复。
在为 API 端点建议修复时,使用适当的 HTTP 状态码(not found 用 404,bad input 用 400,conflicts 用 409)。绝不要建议有意返回 500。
不要规定你自己的输出格式 —— 响应结构由评审框架控制。