Chainlink fix-flaky-tests Skill 深度解析:基于 JQL 的 Flaky Test 工单检索与认领流程

原创2026-09-15 10:15:141,769 阅读
文章标签:区块链Web3后端

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 共享操作的调度逻辑,其中明确了三种取票路径:

  1. 用户提供了具体 JIRA 工单 → 走 claim-ticket 流程;
  2. 用户要求处理 N 个符合条件的工单 → 走 fetch-flaky-tickets 循环;
  3. 否则 → 尝试为待修复测试找到关联的 JIRA 工单,即本文档描述的 find-flaky-test-ticket 流程。

三条路径最终都要求以统一的 slim record 结构在系统内传递数据,避免对 Atlassian MCP 的重复调用。

二、触发前提:什么时候需要按测试名找工单

文档在 <requirements> 中给出两个明确前提:

  1. 用户既未提供 JIRA 工单,也未提供项目(Neither JIRA ticket nor project provided by the user);
  2. 能确定当前仓库——通过 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 三步执行认领

校验通过后,步骤必须顺序执行、每步成功才继续:

  1. 调用 mcp__atlassian__getJiraIssue 读取 fields.assignee.accountId,保存为 original_assignee(无此字段或未指派则为 null);
  2. 调用 mcp__atlassian__editJiraIssue 将 assignee.accountId 设为当前用户,等待成功;
  3. 除非工单已是"当前用户 + 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/testXxx token;
  • 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)。

八、关键设计要点回顾

  1. 检索保守化:JQL 以 labels = flaky-test AND status = Open 圈定工单池,测试名用 ~ 模糊匹配以覆盖子测试;多命中时只呈现不认领,把选择权交还用户。
  2. 有副作用操作前置校验:认领前必过"状态 + 仓库 + 测试存在性"三道闸,任一失败都写入 skip_reason 并安全退出。
  3. 原子引用 + 统一契约:每个 JIRA 操作是独立、自包含的参考文件,系统间数据一律以 slim record 传递,杜绝重复 API 调用与原始 JIRA 对象外泄。
  4. 历史可追溯:调查结论按固定五段模板写入工单评论并可被机器解析回 previous_attempts,让 flaky 测试的修复过程跨人、跨时间可累积。

对维护大型 Go 单测体系(如 Chainlink 这种共享单一 Postgres 测试库的项目)的团队,这套"JQL 检索 → 校验认领 → diagnose 复现 → 结构化回写"的 Agent 化流程,提供了一个可以直接借鉴的 flaky 测试治理范式。

登录后查看全文
chainlink