Flutter PR 失败检查定位与 LUCI 日志追踪:flutter-pr-checks-finder 技能实战指南
本文围绕 Flutter 仓库中的 flutter-pr-checks-finder 技能展开,系统讲解如何在一个 Pull Request 上快速找到所有失败的 CI 检查,并进一步定位到 LUCI(Chromium 基础设施)上的原始构建日志。读完本篇,你将掌握:gh CLI 与 GitHub API 两种检查查询方式(含分页陷阱)、LUCI 日志 URL 的构造规则,以及 Flutter 各类 recipe(flutter_drone、builder.py、tester.py)从 builder 名称到日志 step 名称的映射规律,从而在排查 PR 红叉时能够直接拿到完整、未被截断的失败输出。
技能定位:它解决什么问题
Flutter 主仓库的 CI 跑在 LUCI(Chromium CI)上,GitHub 侧的检查项(check run)只是入口:GitHub 上看到的检查摘要常常被截断、缺乏上下文,真正的错误详情藏在 LUCI 的 raw log 里。flutter-pr-checks-finder 就是为此设计的 Agent 技能,它的官方描述是(SKILL.md):
Find failing checks on a Flutter PR and locate the corresponding LUCI log URLs.
该技能存放于仓库的 .agents/skills/ 目录。这个目录是 Flutter 贡献者共享的 Agent 技能库,收录了分析 flaky 测试、解析 Dart 日志失败、查找回归版本等一系列 CI 排障技能。flutter-pr-checks-finder 正是其中"定位失败检查"这一环:先回答"哪些检查挂了",再回答"完整日志在哪里"。
前置条件
- 已安装并完成认证的
gh(GitHub CLI)。如果它不在 PATH 中,可检查常见安装位置:macOS 上通常是/opt/homebrew/bin/gh,Windows 上通常是C:\Program Files\GitHub CLI\gh.exe。 - 具备
curl或类似工具,用于从 LUCI 抓取原始日志。
第一步:找出所有失败的检查
技能给出两条路径:优先使用 gh CLI,不可用时回退到 GitHub REST API。
方式 A:使用 gh CLI(推荐)
一条命令列出 PR 的所有检查:
gh pr checks <PR_NUMBER>
输出中结论为 failure 的检查项即为失败项。
方式 B:通过 GitHub API 直接发 HTTP 请求
当 gh 不可用时(例如在无认证环境的 Agent 中),可以直接请求公共 GitHub API,分两步完成:
1. 找到 PR 的 SHA
请求 https://api.github.com/repos/flutter/flutter/pulls/<PR_NUMBER>,从响应中提取 head.sha 字段。
2. 列出 Check Runs
请求 https://api.github.com/repos/flutter/flutter/commits/<PR_SHA>/check-runs 并解析 JSON 响应。
关键陷阱:必须处理分页,否则会漏掉失败项。 检查 total_count 字段:如果它大于 check_runs 数组中的实际条数(单页通常被 per_page 限制,默认上限 100 条),就必须追加后续请求:在原 URL 上拼接 ?per_page=100&page=<N>,逐页翻取直到取完全部 check run。之后才能可靠地识别所有 conclusion 为 failure 的检查项。这一步是整个流程中最容易出错的环节——单页只拿到 100 条时,第 101 条之后的失败检查会静默丢失。
第二步:获取失败日志的原始 URL
对每一个失败的检查项,按以下三步操作:
1. 找到 Log URL
- 优先查找该检查项关联的 target URL / 链接。
flutter-dashboard链接通常显示在 GitHub 检查视图底部,文案为 "View more details on flutter-dashboard"。- 也可以根据失败检查的 builder 名称和 build number(如果可得)直接构造 LUCI 页面链接,URL 结构为:
https://ci.chromium.org/ui/p/flutter/builders/try/<Builder Name>/<Build Number>/overview
2. 构造 Raw Log URL
- 手动方式:在 log URL 后追加
?format=raw;或遵循如下模式(<Step Name>的映射规则见下一节):
https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/<Build ID>/+/u/<Step Name>/stdout?format=raw
3. 抓取 Raw Log
用 curl 等工具获取该 raw log URL 的内容。技能在此特别强调:必须使用 raw log URL,以避免 HTML 格式化和输出截断;不要仅依赖 GitHub API 返回的检查摘要,它可能被截断或缺少完整上下文。
Builder 到 Step 名称的映射
技能原文提醒:step 名称可能非常具体且难以猜测(Step names can be very specific and hard to guess)。本节记录的就是帮助推断它们的各种模式。
模式一:flutter_drone Recipe
- 来源:来自 cocoon 或 recipes 仓库(此处仅指仓库名,不作为外部链接引用)。用于运行分片(sharded)的框架测试与 lint,例如
analyze、test_general等,其分片划分由根目录的 .ci.yaml 指定。 - Step 名称模式:
run test.dart for <shard> shard and subshard <subshard> - URL 转换规则:空格替换为下划线。
- 默认值:若未指定
subshard,默认为None。 - 示例:
Linux analyze(shard 为analyze,无 subshard)对应的 step 名称是run_test.dart_for_analyze_shard_and_subshard_None。
这一模式在仓库中有直接佐证。根目录 .ci.yaml 文件头注释明确写道:
The "flutter_drone" recipe just defers to dev/bots/test.dart in this repo, with the shard set according to the "shard" key in this file.
即 flutter_drone recipe 实际上就是转调本仓库的 dev/bots/test.dart,shard 取自 .ci.yaml 中的 shard 键。以 Linux analyze 为例(.ci.yaml 第 365-376 行):
- name: Linux analyze
recipe: flutter/flutter_drone
timeout: 60
properties:
shard: analyze
dependencies: >-
[
{"dependency": "ktlint", "version": "version_1_5_0"},
{"dependency": "open_jdk", "version": "version:21"}
]
tags: >
["framework","hostonly","shard","linux"]
recipe 为 flutter/flutter_drone、shard 为 analyze 且无 subshard,与技能中给出的示例 step 名称 run_test.dart_for_analyze_shard_and_subshard_None 完全对应。而 dev/bots/test.dart 文件头的注释也解释了 shard / subshard 的语义:本地可通过环境变量 SHARD 和 SUBSHARD 指定,如 SHARD=framework_tests、SHARD=build_tests SUBSHARD=1_2(表示两分片的第一片)、SHARD=web_tests SUBSHARD=2(零基索引的第三片),这与 LUCI 上由 recipe 生成的 step 命名规则是同一套约定。
模式二:builder.py 及相关 Recipes
- 来源:位于
flutter/engine仓库(或 recipes 仓库),按照 engine/src/flutter/ci/builders/ 目录下的 target JSON 配置执行构建与测试。 - Step 名称模式:通常是 JSON 配置中的 task 名称,常常带
test:前缀。 - URL 转换规则:空格替换为下划线。
- 陷阱(Gotcha):如果 JSON 文件里的测试名本身已经以
test:开头,recipe 仍可能再叠加一次前缀,例如出现test:_test:_Check_formatting这种双重前缀的怪名字——按"直觉"去掉一层前缀去猜 step 会 404。
builders 目录下的 JSON 即为这些任务的定义来源,例如 engine/src/flutter/ci/builders/linux_host_engine_test.json 中的每个 build 都包含 tests 数组,每个条目给出 name、script 与 parameters:
"tests": [
{
"language": "python3",
"name": "Host Tests for host_debug",
"script": "flutter/testing/run_tests.py",
"parameters": [
"--variant", "ci/host_debug_test",
"--type", "dart,dart-host",
"--engine-capture-core-dump"
]
}
]
从源码结构看,JSON 里的 name(如 "Host Tests for host_debug")就是构造 LUCI step 名称的原始材料,套用"空格转下划线"规则后再处理前缀问题即可。
模式三:tester.py
- 说明:engine CI recipes 中常用的辅助测试脚本。
- Step 名称模式:
Run <shard> tests或Run <shard> <subshard> tests。 - URL 转换规则:空格替换为下划线。
在 Engine 侧精确定位任务名
如果按上述模式猜测失败,可以从 engine 的配置里反查精确名称:
- 在 engine/src/flutter/.ci.yaml 中查找目标 builder,找到其
config_name属性(该文件中大量 target 都以config_name: <platform>_engine...的形式指向 builders JSON,例如linux_host_engine、linux_clang_tidy等); - 到 engine/src/flutter/ci/builders/ 目录定位对应的 JSON 文件;
- 读取 JSON,在
tests数组中找到具体的tasks/ 测试条目名称。
例如从 engine/src/flutter/.ci.yaml 中的 config_name: linux_host_engine 出发,即可找到 engine/src/flutter/ci/builders/linux_host_engine.json 并核对其中定义的测试任务。
兜底方案
如果对猜测的 step 名称请求 raw log 返回 404,说明 step 名称与模式不完全一致。此时应回到 LUCI 的 overview 页面(或从其他来源)直接查看实际执行的 step 名称,再据此抓取日志,而不是继续盲目尝试变体。
小结:把三个环节串起来
整个技能的工作流可以压缩为一条链路:
- 枚举失败:
gh pr checks <PR_NUMBER>或 GitHub API(注意total_count分页)找出所有conclusion: failure的检查; - 定位日志:从检查的 target URL / flutter-dashboard 链接进入 LUCI,或直接按
ci.chromium.org的 builder/build 结构拼 URL; - 抓取原文:把 step 名称按
flutter_drone(run test.dart for <shard> shard and subshard <subshard>)、builder.py(task 名,注意test:前缀陷阱)、tester.py(Run <shard> tests)三类模式映射出来(空格转下划线),拼出logs.chromium.org/...stdout?format=raw并用curl拉取。
配合根目录 .ci.yaml 的 shard 定义、dev/bots/test.dart 的分片机制以及 engine/src/flutter/ci/builders/ 下的任务 JSON,这套流程覆盖了 Flutter 框架侧与引擎侧两类 CI 的日志定位场景。掌握它之后,面对任何一条挂掉的 Flutter PR 检查,都能在分钟级拿到未经截断的完整失败输出,为后续的根因分析(例如使用同目录下的 dart-log-failure-parser 等技能解析日志)打下基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00