Flutter analyze-github-flake 技能详解:从 GitHub Flaky 工单到 LUCI 构建日志的自动化归因
【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter
本文基于 Flutter 仓库中的 Agent 技能文档 .agents/skills/analyze-github-flake/SKILL.md,完整讲解analyze-github-flake技能的工作流程:如何用gh命令识别并解析 fluttergithubbot 自动创建的 flaky 工单,如何从工单正文与评论中提取 LUCI 构建地址,如何通过 Buildbucket prpc API 拉取失败构建的日志元数据,并将多次失败归类为不同失败类型,输出高层摘要。读完后你可以独立复现这一整套 CI 抖动(flake)分析流程,并理解它与 docs/infra/Reducing-Test-Flakiness.md 中描述的 flaky 治理机制之间的衔接关系。
1. 背景:一个 Flaky 工单在 Flutter 仓库中的生命周期
要理解这个技能为什么长这样,先看它在整条 flaky 治理链路中的位置。根据 docs/infra/Reducing-Test-Flakiness.md:
- 每周,Cocoon 中的自动化脚本会扫描过去 15 天的测试执行统计,识别抖动最严重的测试;
- 当某个测试 builder 的Flaky Ratio ≥ 2%时,脚本会(若不存在)自动创建跟踪工单,默认指派给对应子团队 TL,并打上
P0标签; - 如果该测试不是分片(shard)测试,脚本还会更新 .ci.yaml 中对应条目标记为 flaky——具体做法是在
bringup: true行上方加一行# TODO(username): github issue url。当前仓库的 .ci.yaml 共约 7800 行,其中存在多处bringup: true条目(如 L703、L739、L2707 等),对应的就是正在 staging 环境观察或被标记的测试; - 工单关闭后有 15 天宽限期,若抖动持续会再次开单;修复后的测试需连续 50 次运行无抖动才能从 .ci.yaml 中移除
bringup: true重新启用。
而自动创建的 flaky 工单(由机器人fluttergithubbot提交)会包含三类关键信息:同一 commit 上近期 flake 的示例及其 LUCI 构建页链接、完整的 "Flaky builds:" 构建列表、以及 Flutter dashboard 上 "Recent test runs" 的过滤链接。analyze-github-flake技能的工作对象正是这类工单——它把"工单 → 构建日志 → 失败归类摘要"这一段高度重复的人工操作固化为可被 Agent 执行的标准化流程。
该技能存放在 .agents/skills/analyze-github-flake/SKILL.md。Flutter 仓库对共享技能有统一规范,见 .agents/skills/README.md:技能作者拥有所有权、需遵循严格的结构化规则、"One Skill Per CLI Tool"(每个 CLI 工具一个专用技能)、以及在合适时指示 Agent 以只读模式访问真实数据以防意外变更——analyze-github-flake正是"只读分析"这一推荐实践的典型落地。
2. 运行前提与三条硬规则
SKILL.md 开头的宿主假设(Host assumptions)声明了执行环境要求:
- 已安装且在
PATH中可用:dart、git、gh(GitHub CLI); gh已完成认证配置。
文档随后给出三条必须遵守的规则,界定了这个技能"做什么、不做什么":
- 不得修改任何文件——只针对某个具体 check 为什么 flaking 提供分析,输出是分析报告而非代码改动;
- 只查看 issue 正文,以及包含更多构建链接的评论;
- 不尝试解决 flake,也不去 flutter/ 代码内部定位根因——只提供"正在发生什么"的高层摘要,并把失败分桶(bucket)为若干不同类型。
第 3 条尤其值得注意:该技能的定位是分诊的第一公里(收集证据、建立分类),而非根因修复。这与 docs/infra/Reducing-Test-Flakiness.md 的分工一致——归类之后,由 TL 接手 triage、重派与修复。
3. 步骤一:用 gh 拉取并验证 Issue
GitHub issue 链接形如https://github.com/flutter/flutter/issues/<issue-number>,其中issue-number是纯数字。技能要求从链接中取出编号,执行(SKILL.md L26):
gh issue view <issue-number> --repo flutter/flutter \ --json=author,body,id,labels,number,state,title,url文档给出的实例是 issue174116:
gh issue view 174116 --repo flutter/flutter \ --json=author,body,id,labels,number,state,title,url--json指定的字段覆盖了后续判断所需的全部元数据:author(判断是否机器人提交)、body(提取 flaky 构建列表)、labels/title(判断是否为 flaky 工单)、state等。
拿到数据后,技能定义了一个短路(short circuit)条件:只有同时满足以下两点,才属于要分析的类型——
- Issue 由
fluttergithubbot提交; - 标题形如
"<ci.yaml target> is X% flaky"(例如标题中的 target 名对应 .ci.yaml 中定义的 check 目标)。
不满足时:如果用户是直接点名要求调用该技能,就告知用户"这个 issue 不是 flake bot 开的";否则(技能是作为通用流程的一部分被触发的)直接回到先前的工作,不做分析。
这里有个可以佐证的细节:标题中的<ci.yaml target>就是 LUCI builder 所对应的 check 目标名。在当前仓库的 .ci.yaml 中,确实定义了以Windows plugin_test为前缀的一族目标,例如Windows plugin_test_android_variants(约 L6674)、Windows plugin_test_android_standard(约 L6695)等,其task_name与目标名一一对应。技能示例里 URL 的 check_name 为Windows%20plugin_test,即 URL 编码后的 "Windows plugin_test",正对应这一命名体系。
4. 步骤二:解析 "Flaky builds:" 并收集全部构建 URL
对于通过验证的 flaky 工单,信息分布在两处,都要采集:
- Issue 正文(body):标题包含 flaking check 的名称;正文中紧跟
"Flaky builds:"头之后,每一行一个URL,列出该 check 失败或抖动的所有构建实例; - 评论(comments):找出所有
"login": "fluttergithubbot"的评论,格式与正文相同——顶部是一个示例 flake 的链接,其下紧跟 "Flaky builds:" 头与完整列表——同样提取其中的 URL。
这意味着一个持续抖动的工单通常会有多轮机器人评论,每一轮对应一批新的 flaky 构建;技能要求把所有轮次的构建 URL 都收集进来,才能形成完整的失败样本集。
每条构建链接的格式为(SKILL.md L38):
https://ci.chromium.org/ui/p/flutter/builders/<bucket>/<check_name>/<buildNumber>/overview以文档示例为例:
https://ci.chromium.org/ui/p/flutter/builders/prod/Windows%20plugin_test/16247/overview其中:
prod是<bucket>(LUCI 构建桶,与 .ci.yaml 中 staging/prod 双环境对应);Windows%20plugin_test是<check_name>(URL 编码的空格即 check 目标名);16247是<buildNumber>,下一步拉取日志的关键参数。
5. 步骤三:调用 Buildbucket prpc API 拉取构建日志元数据
对每个构建 URL,取出其中的<buildNumber>,执行如下 curl 请求(SKILL.md L45-L63 原文命令,将<bucket>、<check_name>、<buildNumber>替换为实际值):
curl 'https://cr-buildbucket.appspot.com/prpc/buildbucket.v2.Builds/GetBuild' \ -H 'accept: application/json' \ -H 'accept-language: en-US,en;q=0.9,es;q=0.8' \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -H 'origin: https://ci.chromium.org' \ -H 'pragma: no-cache' \ -H 'priority: u=1, i' \ -H 'referer: https://ci.chromium.org/' \ -H 'sec-ch-ua: "Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"' \ -H 'sec-ch-ua-mobile: ?0' \ -H 'sec-ch-ua-platform: "macOS"' \ -H 'sec-fetch-dest: empty' \ -H 'sec-fetch-mode: cors' \ -H 'sec-fetch-site: cross-site' \ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36' \ -H 'x-return-encrypted-headers: all' \ --data-raw '{"builder":{"project":"flutter","bucket":"<bucket>","builder":"<check_name>"},"buildNumber":<buildNumber>,"mask":{"fields":"id,builder,builderInfo,number,canceledBy,createdBy,createTime,startTime,endTime,cancelTime,status,statusDetails,summaryMarkdown,output,steps,tags,schedulingTimeout,executionTimeout,gracePeriod,ancestorIds,retriable"}}'命令结构要点:
- 请求目标是 Buildbucket 的
buildbucket.v2.Builds/GetBuildprpc 端点,即 LUCI 构建系统的构建查询接口;builder对象由project: "flutter"、上一步解析出的bucket与builder(即 check_name)三个字段确定,buildNumber是数字; mask.fields显式声明了要返回的字段集,其中最关键的是:status/statusDetails(构建终态与状态详情)、summaryMarkdown(构建摘要)、output、steps(各执行步骤,含日志引用,是判断失败发生位置的核心)、tags(构建标签)、canceledBy/cancelTime(是否被取消)、executionTimeout(执行超时信息)等。这些字段恰好覆盖了"区分测试失败、构建取消、超时"等失败类型的判断所需;- 请求头中的
origin/referer/sec-fetch-*/user-agent组合是模拟浏览器端 ci.chromium.org 页面调用该接口时携带的上下文,直接照抄即可复用。
响应解析注意事项(SKILL.md L65):Buildbucket 的 prpc 响应开头会带一段)]}'前缀(这是防止 XSSI/JSONP 劫持的常见做法),处理时应忽略这几个字符,把剩余部分当作 JSON 解析。
6. 步骤四:逐条检查日志、按失败类型分桶、输出高层摘要
技能的最后一步(SKILL.md L67-L69):检查日志内容以判定失败原因;对 issue 中列出的每一个 URL 都执行上述拉取与检查,收集每一次具体失败的日志,然后按失败类型归类(categorize),形成一份高层摘要。
结合技能的三条硬规则,这一阶段的输出形态是:一组互斥的失败类型桶(例如某类断言失败、某类设备/环境问题、超时/取消等,具体类别取决于实际日志),每桶下挂对应的 flaky 构建清单,外加一段"正在发生什么"的高层叙述——而不是修改flutter/内代码的修复方案。
当归类摘要完成后,后续的人工 triage 可以按 docs/infra/Reducing-Test-Flakiness.md 的建议继续深入,这些建议与该技能的分类结果直接衔接:
- 区分基础设施问题与测试问题:设备类问题(如设备未找到、测试中途设备掉线、Xcode 缓存污染、瞬时网络问题)往往从 stdout 难以判断,应查看 LUCI 构建页失败步骤的
execution details,在日志底部找非零退出码; - 定位失败的测试:在
test_stdout中搜ERROR:(注意带冒号),其上方会有RUNNING:;Dart/Flutter 测试关注Failed assertion:,shard 单测抖动则搜[E]标记; - 找规律:利用工单中 dashboard 的 "Recent test runs" 过滤链接,验证部分(而非全部)失败是否同型、回找"第一个红色构建"及其 commit、对比成功运行(检查测试顺序依赖、每日首次构建与 bot 每日重新置备的关系、是否集中在同一台 bot/设备上以判断硬件问题)。
7. 技能的校验与维护
按 .agents/skills/README.md 的规范,.agents/skills下的共享技能入库前必须满足:作者实际使用过、明确面向 Flutter 贡献者、PR 中附带提示词与产出示例、命名符合规范、遵循开放技能标准。技能作者对其拥有所有权并负责审批修改。
对analyze-github-flake这类技能的自动化校验,可在dev/tools目录下执行:
# 检查技能:尾随空白、绝对路径、相对路径有效性 dart run dart_skills_lint:cli --skills-directory ../../.agents/skills \ --check-trailing-whitespace --check-absolute-paths --check-relative-paths # 自动校验测试 dart test test/validate_skills_test.dart另有--fix(预览可修复项,dry run)与--fix-apply(自动应用可修复规则)两个作者辅助参数。从源码结构看,仓库内还配套有dev/tools/skills_lint.yaml等配置支撑这套 lint 流程。
8. 流程速查
| 步骤 | 动作 | 关键产物 |
|---|---|---|
| 0 | 环境自检 | dart、git、gh在 PATH 且gh已认证 |
| 1 | gh issue view <n> --repo flutter/flutter --json=...拉取元数据 | author/body/labels 等字段 |
| 2 | 校验fluttergithubbot+ 标题"<target> is X% flaky",否则短路 | 确认是 flaky 工单 |
| 3 | 从 body 与各轮 bot 评论的 "Flaky builds:" 列表提取全部 URL | 构建 URL 集合 |
| 4 | 解析ci.chromium.orgURL 的<bucket>/<check_name>/<buildNumber> | 三元组参数 |
| 5 | curl 调用GetBuildprpc(忽略)]}'前缀解析 JSON) | 状态、steps、summaryMarkdown 等 |
| 6 | 逐条检查日志,按失败类型分桶,输出高层摘要 | 分类报告(只读,不改代码) |
整条链路完全只读:从工单解析到日志归类,技能不触碰仓库任何文件,其产出为后续按 docs/infra/Reducing-Test-Flakiness.md 流程修复并更新 .ci.yaml 提供了证据基础。
9. 延伸阅读(仓库内路径)
- 技能本体:.agents/skills/analyze-github-flake/SKILL.md
- 技能体系规范与 lint 校验:.agents/skills/README.md
- Flaky 治理完整工作流(预防、检测、triage、修复):docs/infra/Reducing-Test-Flakiness.md
- CI 目标与
bringup: true标记定义:.ci.yaml - 其他相关技能(如失败日志解析):.agents/skills/dart-log-failure-parser/SKILL.md
【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考