Chainlink fix-flaky-tests Skill 深度解析:基于 JQL 的 Flaky Test 工单检索与认领流程
Chainlink fix-flaky-tests Skill 深度解析:基于 JQL 的 Flaky Test 工单检索与认领流程
Chainlink 仓库内置了一套面向 AI Agent 的 fix-flaky-tests 技能,用于系统化地诊断和修复 Go 测试的偶发失败(flake)、竞态(race)、超时(timeout)与死锁。本文聚焦该技能中的 flind-flaky-test-ticket.md 参考文档,详解“当用户没有直接提供 JIRA 工单时,如何通过 JQL 按测试函数名自动检索 flaky-test 工单并完成认领”的完整流程;读完后你能掌握该 JQL 语句的构造规则、多命中时的处理方式、认领前的三重前置校验,以及与 slim record、工单状态机、本地 diagnose 测试 harness 的协作关系。
一、文档定位:fix-flaky-tests 技能生态中的一环
该文档位于 tools/test/.agents/skills/fix-flaky-tests/ 技能的 references/ 目录下,是一个"可包含引用"(includable reference)文件——它本身不是独立的用户技能,而是供 Agent 在执行对应操作时按需读取的步骤说明。技能入口 SKILL.md 声明了诊断 flaky 测试的绝对约束、初始化流程和诊断循环;jira.md 则定义了所有 JIRA 共享操作的调度逻辑,其中明确了三种取票路径:
- 用户提供了具体 JIRA 工单 → 走
claim-ticket流程; - 用户要求处理 N 个符合条件的工单 → 走
fetch-flaky-tickets循环; - 否则 → 尝试为待修复测试找到关联的 JIRA 工单,即本文档描述的
find-flaky-test-ticket流程。
三条路径最终都要求以统一的 slim record 结构在系统内传递数据,避免对 Atlassian MCP 的重复调用。
二、触发前提:什么时候需要按测试名找工单
文档在 <requirements> 中给出两个明确前提:
- 用户既未提供 JIRA 工单,也未提供项目(Neither JIRA ticket nor project provided by the user);
- 能确定当前仓库——通过
git remote get-url origin提取出{owner}/{repo}形式。
这意味着该流程是"兜底路径":开发者只是说"帮我修一下 core/services/vrf 里那个不稳定的 TestVRFSubscription",没有附带任何工单信息时,Agent 需要自己用测试名反查 JIRA,确认是否有对应工单可认领,避免无主修复或重复劳动。当前仓库标识的提取方式(git remote get-url origin)在后续的仓库匹配校验中还会再次用到。
三、JQL 检索语句详解
3.1 基础查询
文档 <search-for-ticket> 第 1 步给出核心 JQL:
Test[Short text]" ~ "[TEST_FUNCTION_NAME]" AND labels = flaky-test AND status = Open
拆解来看,该查询包含三个条件:
Test<a href="https://link.gitcode.com/i/98dc422e16c146a5183ea1ae8b1c7436" target="_blank">Short text]模糊匹配:~是 JQL 的"包含"运算符,[TEST_FUNCTION_NAME]会被替换为目标测试函数名(如TestVRFSubscription)。这对应了 [slim-record.md 中test_name字段的取值来源——工单上的customfield_13007字段保存的是完整测试路径(形如TestFoo | TestFoo/subtest_name),因此用模糊匹配而非精确相等,才能同时命中顶层函数及其子测试工单;labels = flaky-test:只查打了flaky-test标签的工单。这与 fetch-flaky-tickets.md 中按项目拉取工单时使用的labels = "flaky-test"过滤条件保持一致,说明该仓库的 JIRA 项目统一用这个标签圈定 flaky 测试工单池;status = Open:只查处于 Open 状态的工单。这与认领前置条件(见第四节)呼应——已被他人认领(状态变化)的工单不会被搜出。
3.2 可选的包名过滤
第 2 步指出:如果已知测试所在的包(package),则追加条件:
AND "package[short text]" ~ "[PACKAGE]"
这里使用的是 JIRA 自定义字段 package 的短文本匹配。对照 fetch-flaky-tickets.md 可知,该自定义字段的实际 ID 是 customfield_13009,保存的是形如 github.com/owner/repo/path 的仓库内路径——slim-record.md 的字段规则(package: customfield_13009)可以印证这一点。加入包名过滤可以显著缩小跨包同前缀测试名的误命中范围。
3.3 多命中时的处理:只列单、不下结论
第 3 步规定:若检索到多于一个工单,Agent 不得自行断定哪一个是目标,而是为每个工单返回一条 slim record,交由用户确认要处理哪一张,然后再执行认领。这体现了该技能体系"宁可少做、不可做错"的保守原则——认领动作会修改工单状态(见下节),属于有副作用的操作,必须建立在用户明确意图之上。
若只有唯一命中(或零命中),第 4 步则直接进入 claim-ticket 流程认领该工单。
四、认领工单:claim-ticket 的三重前置校验与三步执行
claim-ticket.md 定义了"把 flaky-test 工单指派给当前用户并流转到 In Progress"的完整协议。find-flaky-test-ticket 流程在第 4 步引用它,因此理解认领细节是完整理解本检索流程的必要环节。
4.1 认领前三重校验(pre-requisites)
(1)工单状态校验:仅当 status == "Open",或 (status != "Open" AND assignee == 当前用户) 时才允许认领;否则不认领,并把"别人正在处理"写入 skip_reason 字段。
(2)仓库校验(零成本检查):从工单的 package 字段(customfield_13009)中,取 github.com/ 之后的第 2、3 段拼出 {owner}/{repo},与当前仓库(由 git remote get-url origin 提取)比对。不匹配则不认领,skip_reason 记录"该工单应在 {repo} 上下文中处理"。这一步正是第二节前提中"当前仓库标识"的实际用途——防止 Agent 把 A 仓库的 flaky 工单认领到 B 仓库里修。
(3)测试函数存在性校验:从 test_name 字段(customfield_13007)提取顶层函数名(第一个 / 之前的部分),字段缺失时回退到标题中最长的 TestXxx token。随后按能力优先级定位测试定义:
- 有 LSP:用 LSP definition lookup;
- 有 Code Review Graph:调用
mcp__code-review-graph__semantic_search_nodes_tool; - 最后手段才用
grep -rl "func {TestName}" .。
找不到该测试函数 → 不认领,skip_reason 记录"该测试不存在于 {repo}"。这避免了对已删除/已改名测试的无效工单执行认领动作。
4.2 三步执行认领
校验通过后,步骤必须顺序执行、每步成功才继续:
- 调用
mcp__atlassian__getJiraIssue读取fields.assignee.accountId,保存为original_assignee(无此字段或未指派则为 null); - 调用
mcp__atlassian__editJiraIssue将assignee.accountId设为当前用户,等待成功; - 除非工单已是"当前用户 + In Progress",否则按 transition-ticket.md 流转到
In Progress。若流转失败则回滚:取消指派,并把失败原因写入skip_reason。
transition-ticket.md 进一步定义了语义目标状态到实际流转名的别名表(如 In Progress 依次尝试 "In Progress"、"In Development"、"Active"、"Start Progress"),并要求:没有任何别名匹配时返回错误而不是悄悄选一个无关状态——这种防御式设计贯穿整个技能。
五、slim record:检索与认领之间的统一数据契约
find-flaky-test-ticket 第 3 步"为每个工单返回 slim record"所依赖的 schema 定义在 slim-record.md:
{
"jira_key": "KEY-NNN",
"title": "string",
"description": "string",
"package": "github.com/owner/repo/path | null",
"test_name": "TestFoo | TestFoo/subtest_name",
"previous_attempts": [
{
"outcome": "MISMATCH | ABANDONED | FIXED",
"date": "YYYY-MM-DD",
"summary": "string",
"excluded_approaches": ["string"],
"rejection_reasons": ["string"],
"recommended_next_step": "string | null",
"full_text": "string"
}
],
"original_assignee": "string | null",
"skip_reason": "string | null"
}
字段规则要点:
package取自customfield_13009,缺失为 null;test_name取自customfield_13007(含子测试的完整路径),缺失时回退为标题中最长的TestXxx/testXxxtoken;previous_attempts按 investigation-comment.md 的解析规则,从工单历史评论(## Investigation Update — {OUTCOME} · {YYYY-MM-DD}结构)中提取以往尝试的结论、日期、排除的方向、否决原因与建议下一步;skip_reason由认领阶段的子代理写入。
previous_attempts 字段尤其关键:它让新接手工单的人能直接看到"别人试过什么、为什么不行",这正是 JIRA 评论中"调查更新"模板(固定五个小节:What was investigated / Hypothesis / What was tried / Why it didn't hold / Recommended next step)存在的意义——把每次调查的结构化沉淀成可机器解析的历史。
六、姊妹流程对照:fetch-flaky-tickets 的批量取票
与本文档的单测试名检索相对,fetch-flaky-tickets.md 实现了"按项目拉取 N 个合格工单"的分页循环,两者共享同一套校验逻辑(仓库校验、测试函数存在性校验)和同一个 JQL 过滤内核(labels = "flaky-test" AND status = "Open" ORDER BY created DESC),区别在于:
- 批量流程以
project = {KEY}为主过滤条件,用nextPageToken游标翻页,并对每条记录额外做仓库匹配与本地测试定位,不合格者以cross_repo++/not_found++计数跳过; - 批量流程既然已完成前置校验,认领时可直接跳过 claim-ticket 的 pre-requisites。
可以推断,这种"校验逻辑单点复用、两个入口共享"的结构是该技能把 JIRA 操作拆成原子引用文件的根本原因——jira.md 明确说每个引用文件都是自包含的(声明输入、步骤、输出),"你不需要读没在用的文件"。
七、与本地 diagnose harness 的衔接
找到并认领工单只是起点。SKILL.md 的 CLI 参考规定:所有测试运行必须从仓库根目录执行
make test ARGS="diagnose [harness_flags] -- [go_test_flags] ./path"
并给出迭代次数与"漏检 flake 概率"的对应表(30 次约 10%、150–500 次约 2–5% 漏检率)以及 Quick / Standard / Deep / Race / Debug 五档运行画像。该命令由 GNUmakefile 的 test 目标实现(构建 tools/test harness 后转发 ARGS),底层 harness 基于 testrig 并在诊断模式下为运行创建独立 Postgres 库,用法与示例详见 tools/test/README.md。工单闭环的最后一步由 jira.md 的逻辑段规定:无论结果如何,都要为每张工单追加调查评论并流转到正确状态——固定(fixed)→ In Review 且保持调查者为本票 assignee 不解绑、放弃 → Open(解绑)、仓库不匹配 → Open(恢复 original_assignee)。
八、关键设计要点回顾
- 检索保守化:JQL 以
labels = flaky-test AND status = Open圈定工单池,测试名用~模糊匹配以覆盖子测试;多命中时只呈现不认领,把选择权交还用户。 - 有副作用操作前置校验:认领前必过"状态 + 仓库 + 测试存在性"三道闸,任一失败都写入
skip_reason并安全退出。 - 原子引用 + 统一契约:每个 JIRA 操作是独立、自包含的参考文件,系统间数据一律以 slim record 传递,杜绝重复 API 调用与原始 JIRA 对象外泄。
- 历史可追溯:调查结论按固定五段模板写入工单评论并可被机器解析回
previous_attempts,让 flaky 测试的修复过程跨人、跨时间可累积。
对维护大型 Go 单测体系(如 Chainlink 这种共享单一 Postgres 测试库的项目)的团队,这套"JQL 检索 → 校验认领 → diagnose 复现 → 结构化回写"的 Agent 化流程,提供了一个可以直接借鉴的 flaky 测试治理范式。