首页
/ Flutter PR 失败检查定位与 LUCI 日志追踪:flutter-pr-checks-finder 技能实战指南

Flutter PR 失败检查定位与 LUCI 日志追踪:flutter-pr-checks-finder 技能实战指南

2026-09-05 20:36:52作者:凤尚柏Louis

本文围绕 Flutter 仓库中的 flutter-pr-checks-finder 技能展开,系统讲解如何在一个 Pull Request 上快速找到所有失败的 CI 检查,并进一步定位到 LUCI(Chromium 基础设施)上的原始构建日志。读完本篇,你将掌握:gh CLI 与 GitHub API 两种检查查询方式(含分页陷阱)、LUCI 日志 URL 的构造规则,以及 Flutter 各类 recipe(flutter_dronebuilder.pytester.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。之后才能可靠地识别所有 conclusionfailure 的检查项。这一步是整个流程中最容易出错的环节——单页只拿到 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

  • 来源:来自 cocoonrecipes 仓库(此处仅指仓库名,不作为外部链接引用)。用于运行分片(sharded)的框架测试与 lint,例如 analyzetest_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"]

recipeflutter/flutter_droneshardanalyze 且无 subshard,与技能中给出的示例 step 名称 run_test.dart_for_analyze_shard_and_subshard_None 完全对应。而 dev/bots/test.dart 文件头的注释也解释了 shard / subshard 的语义:本地可通过环境变量 SHARDSUBSHARD 指定,如 SHARD=framework_testsSHARD=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 数组,每个条目给出 namescriptparameters

"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> testsRun <shard> <subshard> tests
  • URL 转换规则:空格替换为下划线。

在 Engine 侧精确定位任务名

如果按上述模式猜测失败,可以从 engine 的配置里反查精确名称:

  1. engine/src/flutter/.ci.yaml 中查找目标 builder,找到其 config_name 属性(该文件中大量 target 都以 config_name: <platform>_engine... 的形式指向 builders JSON,例如 linux_host_enginelinux_clang_tidy 等);
  2. engine/src/flutter/ci/builders/ 目录定位对应的 JSON 文件;
  3. 读取 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 名称,再据此抓取日志,而不是继续盲目尝试变体。

小结:把三个环节串起来

整个技能的工作流可以压缩为一条链路:

  1. 枚举失败gh pr checks <PR_NUMBER> 或 GitHub API(注意 total_count 分页)找出所有 conclusion: failure 的检查;
  2. 定位日志:从检查的 target URL / flutter-dashboard 链接进入 LUCI,或直接按 ci.chromium.org 的 builder/build 结构拼 URL;
  3. 抓取原文:把 step 名称按 flutter_dronerun test.dart for <shard> shard and subshard <subshard>)、builder.py(task 名,注意 test: 前缀陷阱)、tester.pyRun <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 等技能解析日志)打下基础。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384