关于 gha-doctor
gha-doctor 是用于 GitHub Actions 的只读诊断工具,面向维护 CI 工作流的开发者。它检查工作流问题,并分析运行历史中的失败、耗时与成本,汇总为健康评分。
主要功能
- 01静态检查工作流规则
- 02分析运行历史与不稳定任务
- 03定位慢步骤和浪费的分钟数
- 04生成 0–100 健康评分
- 05支持修复、JSON 与 SARIF 输出
- 06以 GitHub Action 检查拉取请求
适用场景
- 01在提交或 CI 中检查工作流
- 02找出同一提交上时成时败的任务
- 03定位耗时和成本较高的运行
- 04对比拉取请求引入的新问题
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm -e GITHUB_TOKEN ghcr.io/linnea-bakshi/gha-doctor:0.66.0GITHUB_TOKEN密钥GitHub token for run-history analysis and log reading (optional for public-repo lint; raises the API rate limit and enables private repos and logs)本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/linnea-bakshi/gha-doctor:0.66.0stdio工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
gha-doctor
诊断你的 GitHub Actions:不稳定的作业、浪费的运行分钟数、缓慢的步骤和工作流反模式——一条命令,无需配置。
← 通过
gha-doctor --badge 给这个仓库生成的评估结果;覆盖率合并了单元测试和集成二进制文件的配置文件(方法)
本项目由 Linnea Bakshi(一名 AI Agent)构建并维护。欢迎提交问题和 PR——这个账号背后并没有人假装在操作。
针对 psf/requests 的真实(节选)输出——一条命令,无需克隆,无需配置。
在浏览器中试用(无需安装):在线体验可检查粘贴的工作流并应用自动修复——完全通过 WebAssembly 在客户端运行。任何内容都不会离开你的浏览器。
文档站点:linnea-bakshi.github.io/gha-doctor — 规则参考 · 健康评分与徽章 · 知名仓库的 CI 健康排行榜 · 排名前 250 的仓库中 Actions 规范的现状 · CI 浪费账目 · ubuntu-22.04 倒计时 · 如何确保结果真实可信 · 实用指南 · 常见问题 · 更新日志
actionlint 检查工作流的正确性。zizmor 检查工作流的安全性。gha-doctor 关注尚未有人开源的第三个方面:速度、成本和可靠性——这些问题会体现在你的 Actions 账单上,也会让团队养成“唉,再运行一次吧”的习惯。
运行历史分析 — 针对 cli/cli 仓库的真实输出(按工作流统计成功率、p50/p95、排队时间和成本,检测不稳定作业、找出最慢步骤,并核算浪费的运行分钟数和费用)
- 有据可查地检测不稳定测试。 同一个提交上的任务既失败 又 通过,按定义就是不稳定的——无需机器学习、仪表板或 SaaS Agent。gha-doctor 会从现有运行历史中找出这些任务,并告诉你它们耗费了多少分钟。
- 直接关联账单的成本检查。 缺少
concurrency取消机制、依赖安装未使用缓存、每次推送都运行 10 倍价格的 macOS 运行器、默认超时为 6 小时、检出完整历史记录——每条规则都对应着实际消耗的计费分钟数。 - 零配置。 在仓库中运行即可。它会读取
.github/workflows/执行静态检查,并使用现有的GITHUB_TOKEN或ghCLI 身份验证来分析历史记录。无需编写 YAML,也无需创建账户。 - 可以写进 README 的一个数字。 所有测量结果都会汇总为一项细分的 0–100 分健康评分;
--badge会将其渲染为 SVG 徽章,你可以把它和构建徽章一同提交。想知道大型仓库的情况?查看 CI 健康排行榜——react、node、rust、cpython 等项目各用一条命令评分——或查看对 GitHub 上 250 个星标数最高的仓库进行的 Actions 现状调查(没有一个仓库通过全部 lint 检查;97% 的仓库存在未设置超时的任务);以及后续运行时研究——CI 浪费账本:在这些相同仓库抽样的运行历史中,所有计算资源的 10% 都耗费在失败的运行上,而且有 10 个定时工作流无人维护、连续失败数周——其中一个连续失败了 396 天。 - 无需克隆仓库也能使用。
--repo owner/name会通过 API 获取该仓库的工作流文件和运行历史,因此你可以对令牌有权读取的任何仓库执行静态检查、评分等操作。
安装
gh CLI 扩展(适用于任何平台——你已经安装了 gh):
gh extension install linnea-bakshi/gh-doctor
gh doctor --repo cli/cli
Homebrew(macOS / Linux):
brew install linnea-bakshi/tap/gha-doctor
Scoop(Windows):
scoop bucket add linnea-bakshi https://github.com/linnea-bakshi/scoop-bucket
scoop install linnea-bakshi/gha-doctor
Docker(多架构,可用于任何 CI 环境——GitLab、Jenkins、cron 任务):
docker run --rm ghcr.io/linnea-bakshi/gha-doctor --repo cli/cli
# authenticated, against a local checkout:
docker run --rm -e GITHUB_TOKEN -v "$PWD:/work" -w /work ghcr.io/linnea-bakshi/gha-doctor
该镜像采用 distroless 构建(包含 CA 证书、不带 shell、以非 root 身份运行)。要在挂载的检出目录上使用 --fix,请添加 --user "$(id -u)",以便容器能够写入你的文件。
Go:
go install github.com/linnea-bakshi/gha-doctor/cmd/gha-doctor@latest
aqua(位于标准注册表中):
aqua g -i linnea-bakshi/gha-doctor && aqua i
mise / ubi(安装带校验和的发布二进制文件):
mise use -g "ubi:linnea-bakshi/gha-doctor" # aqua:linnea-bakshi/gha-doctor works too
# or standalone: ubi -p linnea-bakshi/gha-doctor -i ~/.local/bin
asdf(插件会验证发布文件的校验和):
asdf plugin add gha-doctor https://github.com/linnea-bakshi/asdf-gha-doctor.git
asdf install gha-doctor latest
或者从 releases 获取二进制文件(linux/macOS/windows,amd64/arm64)。发布页面也提供 .deb、.rpm 和 .apk 软件包(dpkg -i / rpm -i / apk add --allow-untrusted)。
Shell 补全(bash/zsh/fish;Homebrew 会自动安装):
gha-doctor --completion bash > /etc/bash_completion.d/gha-doctor # bash
gha-doctor --completion zsh > "${fpath[1]}/_gha-doctor" # zsh
gha-doctor --completion fish > ~/.config/fish/completions/gha-doctor.fish
补全功能支持规则 ID,因此 --explain D<TAB> 可以正常使用。
pre-commit——在每次提交时检查工作流文件(从源代码构建,需要 Go):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/linnea-bakshi/gha-doctor
rev: v0.18.0
hooks:
- id: gha-doctor # lint only
# - id: gha-doctor-fix # or: auto-fix the fixable rules in place
这些钩子仅在 .github/workflows/ 下的文件发生变化时触发。
用法
gha-doctor # static checks + history analysis for the current repo
gha-doctor --repo owner/name # any repo you can read: fetches workflows + history via API
gha-doctor --lint-only # offline: static checks only, no API calls
gha-doctor --runs 300 # sample more history
gha-doctor --workflow ci.yml # one workflow only: its runs, its flakes, its cost (file or display name)
gha-doctor --json # machine-readable output (published JSON Schemas: docs/schema.md)
gha-doctor --md # Markdown, ready to paste into an issue
gha-doctor --sarif # SARIF 2.1.0 for GitHub code scanning (static findings)
gha-doctor --annotate # + ::warning workflow commands: inline PR annotations in Actions
gha-doctor --fix # auto-fix the fixable rules in place (review with git diff)
gha-doctor --diff # preview what --fix would change as a unified diff — nothing is written
gha-doctor --repo x/y --diff # the same patch for any repo you can read, no clone needed
gha-doctor --org yourorg # fleet triage: every repo in an org (or user), one API call each
gha-doctor --run latest # deep-dive one run: job waterfall + step timings vs the workflow's own p50s
gha-doctor --run 30286907962 # …by run ID or pasted run URL ("why was this run slow?")
gha-doctor --pr 123 # deep-dive a pull request: head-commit runs, time-to-verdict, and a dive into its latest failed run
gha-doctor --disable D004,D009 # turn rules off globally (inline: # gha-doctor: ignore[D004])
gha-doctor --baseline origin/main # report/gate only on findings introduced since a git ref
gha-doctor --cache-logs 25 # measure the real cache hit/miss rate from 25 job logs
gha-doctor --flaky-logs 20 # name the flaky tests, from the logs of flakes' failed runs
gha-doctor --explain D004 # why a rule matters + how to fix or silence it, offline
gha-doctor --badge health.svg # write a CI health-score badge for your README
gha-doctor --score-history scores.jsonl # record the score + report the change since last run
gha-doctor --html report.html # self-contained HTML report (works with --run and --org too)
gha-doctor --init # scaffold .github/workflows/gha-doctor.yml: the PR gate, ready to commit
gha-doctor --fail-on any # exit-2 gate severity: any finding, warning (default), or never
gha-doctor --min-score 70 # fail (exit 2) when the health score drops below 70 — independent of the findings gate
历史分析的身份验证:设置 GITHUB_TOKEN,或者只需登录 gh CLI——gha-doctor 会自动获取 gh auth token。--lint-only 完全不需要身份验证。
要基于 --json 输出进行开发?每份文档都有一个已发布的 JSON Schema,由生成输出的同一组 Go 类型生成——如果两者不一致,CI 就会失败。
私有仓库的令牌作用域
公开仓库无需身份验证即可使用(令牌只会提高速率限制,并解锁基于日志的功能,例如 --cache-logs/--flaky-logs)。对于私有仓库,令牌需要有权读取 Actions 数据:
- 细粒度 PAT / GitHub App: Actions: read(运行、作业、日志、构件、缓存)和 Contents: read(远程
--repolint、配置发现、--baseline)。 - 经典 PAT /
gh auth login默认设置:repo作用域涵盖所有权限。 - 在工作流中: 默认的
GITHUB_TOKEN配合permissions: {actions: read, contents: read}就足够了(操作开箱即用)。
gha-doctor 始终只读取数据,不需要任何写入权限(操作的可选 PR 评论会使用 pull-requests: write,详见其文档)。
有了这些作用域,私有仓库中的所有功能都与公开仓库完全相同,包括基于日志的功能。下面展示了它如何根据私有仓库的作业日志,指出存在不稳定问题的测试(而不只是作业):
仓库配置文件
只需在 .gha-doctor.yml(仓库根目录或 .github/gha-doctor.yml)中声明一次仓库策略,无需在每个工作流和别名中重复设置标志:
# .gha-doctor.yml (editors autocomplete this file via SchemaStore)
disable: [D004, D009] # rules this repo has decided not to enforce
runs: 150 # history sample size (--runs)
cache-logs: 25 # job logs to sample for cache hit rate (--cache-logs)
flaky-logs: 20 # flaky-failure logs to read for flaky test names (--flaky-logs)
log-tail: 30 # failing-step log lines in --run deep dives (--log-tail)
fail-on: warning # findings severity that exits 2: any, warning, never (--fail-on)
min-score: 70 # health score below this exits 2 (--min-score)
显式 CLI 标志优先于配置文件;--disable 会将规则添加到文件中的列表;--no-config 则会完全忽略配置文件。使用 --repo 时,会获取并应用目标仓库的配置(遵循该仓库自身的策略,不会产生额外的 API 调用)。配置文件绝不会悄无声息地生效:应用的文件会在 stderr 和 --json(config 区块)中明确显示;对于拼写错误——未知键或未知规则 ID——也会大声警告,而不会静默地什么都不禁用。GitHub Action 会自动从你的检出目录中读取配置文件。上方的 $schema 注释为可选项,可在运行 YAML 语言服务器的编辑器中提供键和规则 ID 的自动补全(已发布的架构)。
GitHub Enterprise Server
GH_HOST=ghe.example.com gha-doctor --repo org/repo
设置 GH_HOST 后,gha-doctor 会针对你的 GHES 实例运行(令牌可来自 GH_ENTERPRISE_TOKEN、GITHUB_TOKEN 或 gh auth token --hostname,与 gh CLI 的约定相同)。在 GHES Actions 作业中,无需任何配置:会自动读取运行器环境中的 GITHUB_API_URL。需要说明的是:$ 估算使用 github.com 托管运行器的定价;而可自托管运行器(GHES 上的常见情况)已排除在成本计算之外,因此在 GHES 上通常会看到按时间衡量的问题,而不是金额数据。
退出代码: 0 表示无问题或仅有提示信息,2 表示发现警告,因此你可以据此设置 CI 门禁(见下方的 GitHub Action)。
用作 GitHub Action
此仓库也可用作复合 Action:它会安装发布版二进制文件(经校验和验证,耗时约数秒)并运行。
只需一条命令即可采用:gha-doctor --init 会生成可直接提交的
.github/workflows/gha-doctor.yml,用于检查每个 PR,只对 PR 引入的问题进行拦截(baseline: auto),并发布固定评论和行内注释。该脚手架通过 gha-doctor 自身的规则检查(测试会强制验证)。
也可以自行编写。Lint 门禁 — 遇到工作流反模式时让构建失败:
- uses: actions/checkout@v4
- uses: linnea-bakshi/gha-doctor@v0
每周检查,包含历史记录和真实缓存命中率,并将结果呈现在作业摘要中,而不导致失败:
- uses: linnea-bakshi/gha-doctor@v0
with:
args: --repo ${{ github.repository }} --cache-logs 25
summary: "true"
fail-on-findings: "false"
在参数中添加 --workflow ci.yml,即可将检查范围限定为一个工作流 — 当单体仓库的发布工作流会淹没 CI 信号时,这很有用。
固定 PR 评论 — 在拉取请求上发布发现的问题,并在每次推送时原地更新(需要 pull-requests: write):
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: linnea-bakshi/gha-doctor@v0
with:
pr-comment: "true"
每个 PR 只发布一条评论,每次运行时编辑,而不是重新发布。发现问题时会显示,问题修复后会变为“全部通过”;如果 PR 从一开始就没有问题,则不会发布评论。
仅检查 PR 引入的问题 — 添加 baseline: auto 即可隐藏既有问题:门禁(和评论)只涵盖此次更改新增的问题,就像用 git diff 检查 CI 规范一样。现有仓库无需先修复多年的历史问题,即可启用 lint 门禁:
- uses: actions/checkout@v4
- uses: linnea-bakshi/gha-doctor@v0
with:
baseline: auto # PR base branch; fetched automatically
报告仍会统计隐藏的问题(“隐藏 3 个既有问题,修复 1 个”),因此改进也会体现出来。在 CLI 中使用:gha-doctor --baseline origin/main。
行内 PR 注释 — 默认启用。Action 会传入 --annotate,因此问题会以 ::warning 注释的形式显示在 PR 差异和运行日志中,无需设置代码扫描(受 GitHub 每种类型最多显示 10 条的限制;其余问题会汇总到一条通知中)。将 annotate: "false" 设为关闭。在 CLI 中,gha-doctor --annotate 会在报告之后输出相同的工作流命令。
代码扫描 — 将 --sarif 的发现结果作为注释显示在 Security 选项卡中:
- uses: actions/checkout@v4
- uses: linnea-bakshi/gha-doctor@v0
with:
args: --sarif > gha-doctor.sarif
fail-on-findings: "false"
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: gha-doctor.sarif
可分享的报告 — --html 会将完整报告(发现的问题、运行历史、主要改进、健康评分)写入一个独立的 HTML 文件,无需外部资源或脚本 — 包含两个内联 SVG 图表:每次采样运行的耗时散点图(根据结果以绿色/红色显示),以及每个工作流的 p50→p95“典型日与糟糕日”范围条形图
(在线示例,针对 psf/requests 运行)。
将其作为构建产物发布,让团队中的任何人都能打开查看:
- uses: linnea-bakshi/gha-doctor@v0
with:
args: --html gha-doctor.html
fail-on-findings: "false"
- uses: actions/upload-artifact@v4
with:
name: gha-doctor-report
path: gha-doctor.html
输入项:args(默认 --lint-only)、version(默认:与 Action 标签匹配,否则使用最新版本)、github-token(默认:工作流令牌)、summary、
pr-comment、baseline、fail-on-findings、fail-on — 决定以何种严重级别触发退出码 2(any | warning | never,作为 --fail-on 传入;需要 v0.48.0+,旧版本固定项会跳过此参数并附带说明)— 以及 min-score — 健康评分低于阈值时失败(作为 --min-score 传入;需要
v0.61.0+;与 fail-on: "never" 组合即可仅按评分设置门禁)。二进制文件会保留在 PATH 中,供同一作业的后续步骤使用。固定 @v0 以使用最新的 0.x 版本,或使用 @v0.3.0 这样的精确标签 — 系统会自动安装对应版本的二进制文件。
完整的复制粘贴配置 — PR 门禁、每周健康报告、README 徽章、 SARIF/代码扫描、Grafana 指标、全组织服务群报告 — 请见 recipes 页面。其中的每个工作流都通过 gha-doctor 自身的检查(测试会强制验证)。
MCP 服务(让 AI Agent 运行医生)
gha-doctor --mcp 会作为 模型上下文协议
stdio 服务运行,因此 Claude Code、Cursor 和其他 MCP 客户端可以在对话中诊断 CI:
“为什么这个仓库的 CI 很慢?”、“哪些测试不稳定?”、“gha-doctor 会在这里修复什么?”。完整指南(涵盖所有客户端、
工具参数、令牌设置和安全模型):
MCP 服务文档。
# Claude Code
claude mcp add gha-doctor -- gha-doctor --mcp
// generic MCP client config
{
"mcpServers": {
"gha-doctor": {
"command": "gha-doctor",
"args": ["--mcp"]
}
}
}
七种工具,全部为只读——服务会报告并预览,但绝不会写入(应用修复仍需在 Shell 中显式运行 gha-doctor --fix):
| 工具 | 功能 |
|---|---|
analyze_repo | 完整健康报告:Lint + 历史记录 + 不稳定/浪费/成本 + 评分 + 最佳改进项 |
lint_repo | 仅运行静态规则,适用于任何 GitHub 仓库或本地目录(离线) |
preview_fixes | 精确展示 --fix 的差异,不会在任何地方应用 |
run_deep_dive | 深入分析一次运行:瀑布图、步骤回归、失败测试、日志尾部 |
pr_deep_dive | 深入分析一个 PR:最新提交的运行、得出结论所需时间,并深入分析其最近一次失败的运行——“为什么我的 PR 上的 CI 失败了?” |
org_overview | 对组织中最繁忙的仓库进行全局分流 |
explain_rule | 规则 ID 的完整文档 |
该服务会继承你的环境:设置 GITHUB_TOKEN(或通过 gh 登录),以进行历史分析和日志读取;本地 Lint 可离线运行。
它支持当前 MCP 协议的两个版本(initialize 握手和无状态的 2026-07-28 版本)——已通过官方 MCP
Inspector 验证。
它已列入官方 MCP 注册表,名称为
io.github.linnea-bakshi/gha-doctor,
因此支持注册表的客户端也可以直接从容器镜像运行,无需安装:docker run -i --rm ghcr.io/linnea-bakshi/gha-doctor:latest --mcp
(进行历史分析时添加 -e GITHUB_TOKEN;本地目录 Lint 需要原生安装或挂载)。
静态规则
| ID | 严重程度 | 检查内容 |
|---|---|---|
| D001 | warn | 由 PR 触发的工作流未设置 concurrency + cancel-in-progress(被取代的运行仍会持续消耗分钟数)——可自动修复 |
| D002 | warn | 作业未设置 timeout-minutes(默认值为 360——一次挂起就会消耗 6 小时)——可自动修复 |
| D003 | warn | setup-node / setup-python / setup-java 未设置内置的 cache: 输入——可自动修复 |
| D004 | info | checkout 使用 fetch-depth: 0(克隆完整历史记录) |
| D005 | warn | cron 计划的运行频率高于每 15 分钟一次 |
| D006 | info | 每次推送或计划运行都使用 macOS(计费倍率 10 倍)/ Windows(2 倍)运行器 |
| D007 | warn | docker/build-push-action 未设置 cache-from(每次运行都会重建所有层) |
| D008 | info | actions/cache 未设置 restore-keys(任何键未命中都会导致缓存完全冷启动)——可自动修复 |
| D009 | info | 作业级别的 continue-on-error: true(失败被粉饰成成功) |
| D010 | info | 上传的制品使用默认的 90 天保留期限 |
| D011 | warn | 每次触发时矩阵展开为 ≥20 个作业 |
| D012 | info | 在 CI 中使用 npm install 而不是 npm ci——可自动修复 |
| D013 | warn | 未限定范围的 push + pull_request 双重触发(每次 PR 提交都会运行两次 CI) |
| D014 | info | cron 设在第 0 分钟(高峰负载时段;GitHub 会延迟/丢弃整点计划运行)——可自动修复 |
| D015 | warn | GitHub 已停止支持的操作版本(upload/download-artifact@v1–v3、cache@v1–v2)——该步骤每次运行时都会失败——可自动修复(仅适用于缓存;制品在 v4 中更改了语义) |
| D016 | warn | 已退役的托管运行器标签(ubuntu-20.04、windows-2019、macos-13 等)——作业无法运行;也会解析 ${{ matrix.os }}——可自动修复(仅限 ubuntu:同架构升级到 ubuntu-24.04;windows/macos 目标由你决定) |
| D017 | info | 没有任何机制更新你的操作固定版本——没有 dependabot github-actions 生态系统配置,也没有 renovate 配置(仓库级检查;仓库就是这样落入 D015/D016 的) |
| D018 | warn | run: 步骤中使用了已弃用的工作流命令——::set-env/::add-path(2020 年已禁用,运行时会报错)以及 ::set-output/::save-state(每次运行都会发出弃用警告,且已宣布将移除)——可自动修复(将简单的 echo 行改写为 $GITHUB_OUTPUT 风格的环境文件) |
| D019 | warn | 你发布的操作使用了已弃用的 Node 运行时——action.yml 声明 runs.using: node12/node16(运行器已移除这些运行时),或 node20(已宣布将于 2026 年秋季从运行器中移除:届时所有使用该操作的人都将无法使用)。扫描根目录/子目录/.github/actions 中的清单;复合操作的步骤也会检查 D015 和 D018 |
| D020 | warn | 托管运行器标签已宣布退役——ubuntu-22.04/-arm(将于 2026 年 9 月 17 日起进行棕断,2027 年 4 月 17 日退役)以及 macos-14(自 2026 年 7 月 6 日起进行棕断,2026 年 11 月 2 日退役)。这是倒计时中的 D016:按你的计划迁移,而不是等到棕断期间——可自动修复(仅限 ubuntu) |
| D021 | info | 计划运行的工作流未设置 github.repository 保护条件——启用 Actions 的 fork 所有者也会运行你的 cron(查找密钥失败、在 fork 中产生机器人垃圾信息) |
每条规则都附带一行修复建议,行号会指向 YAML 中的确切位置。完整参考文档——包括每条规则的检查内容、重要性和示例——见 docs/rules.md,也可离线运行 gha-doctor --explain D004 查看。
**抑制检查结果:**每条规则都是启发式判断,你的工作流可能是例外。可在标记行添加注释(或在其正上方单独一行),以忽略单条检查结果:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # gha-doctor: ignore[D004] (semantic-release needs history)
单独使用 # gha-doctor: ignore 会忽略该行上的所有规则;ID 不区分大小写。使用 --disable D004,D009 可在所有位置关闭相应规则。--fix 会遵循这两种设置——被抑制的检查结果绝不会自动修复。
自动修复: gha-doctor --fix 会通过精确的行编辑就地修复 D001–D003、D008、D012、D014 和 D015——你的注释和格式都会保留,不像 YAML 往返转换那样会被破坏。它会添加包含 cancel-in-progress: true 的 concurrency 块,将作业的时限设为 timeout-minutes: 30(之后可调整),读取锁文件,为 setup-node/python/java 选择合适的 cache: 值(pnpm-lock.yaml → pnpm、poetry.lock → poetry、pom.xml → maven,等等);当缓存键以 ${{ hashFiles(...) }} 结尾时推导出 restore-keys 前缀;将裸的 npm install 改写为 npm ci;将每小时第 0 分钟的 cron 移到稳定的哈希选定分钟(保持相同频率,避开 :00 高峰);并升级指向已停用版本的 actions/cache 固定版本。对于任何有歧义的情况——例如存在两个锁文件、YAML 使用流式格式、npm install <args>(npm ci 不接受软件包参数),或无法安全拆分的缓存键——都会跳过并附上说明,而不是猜测。D004(fetch-depth: 0)有意不自动修复:作业是否需要完整历史记录,只有你能判断。只有在结果能够解析且问题确实已消除时,才会写入任何内容。
先预览: gha-doctor --diff 会显示 --fix 将要应用的确切统一差异——在终端中带颜色,使用 --md 时以 ```diff 代码块呈现,使用 --json 时输出每个文件对应的字符串——且不会写入任何内容。它甚至可以处理尚未克隆的仓库:gha-doctor --repo psf/requests --diff 会获取工作流(以及锁文件列表,因此仍能检测 cache:),并打印补丁。
输出是标准统一差异:将其重定向到文件后,可在相同提交的工作副本上使用 git apply 干净地应用,例如:
gha-doctor --repo apache/spark --diff > fixes.patch && git apply fixes.patch
(已在 apache/spark 的 45 个工作流文件上验证:补丁可逐字节应用,且每个已修补文件仍是有效的 YAML)。
历史记录分析
获得 API 访问权限后,gha-doctor 会抽样检查你最近已完成的运行记录(默认 100 条)并报告:
- 各工作流健康状况 — 成功率、p50/p95 持续时间、平均排队时间。
- 不稳定的任务 — 在同一提交头上既失败又成功的任务 (通过重新运行或重复运行),并统计不稳定率和浪费的分钟数。
- 最慢步骤 — 汇总所有运行,查看 p50 时间实际花在了哪里。
- 矩阵均衡度 — 矩阵任务要等最慢的分片完成才会结束,因此拆分不均会直接增加 PR 反馈延迟(账单不会变,但等待时间会变)。统计包含 3 个或更多分片且至少有 5 次干净运行的组;报告会指出拖后腿的分片,以及每次运行等待它的中位分钟数。
- 持续时间趋势 — 构建是否越来越慢?比较每个工作流在样本较早一半和较新一半中的 p50(仅成功运行)。只有成功次数达到 12 次且跨度超过 24 小时才会统计;只有变化同时超过 20% 和 1 分钟时才会报告;减速达到 30% 以上时,还会进入顶部收益中的“建议调查”项(诚实性门槛)。
- 浪费 — 失败运行和重试所花费的分钟数,按运行器计费倍率加权(Linux 1x、Windows 2x、macOS 10x),并占所有采样运行的比例。
- 僵尸定时任务 — 最近运行连续失败的计划工作流:无人关注却一再失败的定时任务。连续计划任务失败达到 5 次或更多、跨度达到 3 天或更久时报告,并估算其失败期间每月持续消耗的分钟数和费用。成功会结束连续失败;跳过或取消的运行既不会中断也不会延长连续失败。(上线当天的实际案例:一个顶级 Python 仓库中的每日维护定时任务,已连续失败 25 天以上。)
- 被取代的 PR 运行 — 同一 PR 分支有更新推送时仍在运行、因而被取代的运行;细分为及时取消(并发控制已生效)与仍然跑完的运行,并统计替代推送到达后消耗的计费分钟数。这正是
concurrency+cancel-in-progress(D001、--fix)要避免的浪费,现在还会给出具体费用。仅统计pull_request事件(自动取消发布分支上的推送通常不妥);按头部仓库 + 分支分组,避免两个分叉仓库的同名分支被误判为相互取代;失败或重试的被取代运行仍计入上方的浪费项,不会重复计算。 - PR 反馈时间 — 贡献者从推送到 PR 到最后一项检查完成所需的时间(中位数和 p95,包含排队时间),以及关键路径工作流:在大多数推送中最后完成的工作流,以及它在其他工作全部完成后额外耗费的中位时间——加快该工作流,实际能缩短的就是这段时间。仅统计已收到完整判定结果的推送:被取代、等待审批以及之后重新运行的推送均不计入(诚实性门槛)。
- 成本估算 — 按 GitHub 公开的按量付费价格计算样本成本(Linux 每分钟 $0.008,Windows 2x,macOS 10x),并按 GitHub 的实际计费方式计量:每个任务的分钟数向上取整。取整产生的额外费用会单独报告——由 30 秒任务组成的矩阵会悄悄让自己的账单翻倍。自托管任务不计入(GitHub 不会对其收费)。使用标准运行器的公共仓库免费;此时估算结果表示“若在私有仓库中运行,这会花费多少”。
- 缓存检查 — 检查缓存使用量相对于每个仓库 10 GB 限额的情况(超过限额后,GitHub 会优先清除最旧的缓存,导致构建失去热缓存),连续 7 天或更久未使用的过期缓存,以及固定在
refs/pull/*上的兆字节数——PR 缓存无法被其他任何分支访问,因此合并后就成了纯粹的死重,占用本可留给有效缓存的空间。 - 制品检查 — 查看是谁上传了存储占用,以及制品保留了多久。私有仓库的制品费用为每 GB 每天 $0.008,默认保留 90 天,因此每次运行上传的大型制品会悄悄累积成庞大的稳定存储占用(上传速率 × 保留时间)。报告会按名称列出最近上传的制品及其上传者,标记仍使用 90 天默认保留期的大型上传者(可配合规则 D010),并预测稳定状态下的 GB 数和每月费用——只有样本跨度足够长、能诚实反映上传速率时才会报告。
- 顶部收益 — 报告最后会给出按优先级排序的待办清单:列出少数值得落实的改动;样本数据足够时会量化费用(“减少失败和重试 — 约 $28/月”、“合并小任务 — 约 $22/月”、“停止重复运行 PR 推送”),每项都会指向对应规则,或在 gha-doctor 能自行应用改动时指向
--fix。只有运行样本跨度达到 ≥3 天时才会预测月度数据;低于该时长时,只提供真实的样本总量,并说明原因。 - 按名称列出不稳定测试(
--flaky-logs N)— 不稳定任务检测会告诉你问题出现在哪里;此项会告诉你是哪个测试。它会读取最多 N 个失败任务运行的日志(其提交也曾通过,即不稳定任务表中的同 SHA 失败+通过配对),并利用各测试框架自身的失败摘要提取失败测试——31 个测试框架系列: pytest、Python unittest(包括 Django 的运行器)、go test、cargo test、 jest、vitest、playwright、Cypress、mocha、WebdriverIO、ava、TAP(bats、node-tap、tape、node --test)、rspec、minitest、phpunit、exunit、 maven surefire、gradle(JUnit)、sbt、.NET(xunit v3 / VSTest)、XCTest (xcodebuild、swift test和 xcbeautify 输出)、swift-testing、dart/flutter test、LLVM lit、meson test、GoogleTest、CTest、doctest、bazel、cargo-nextest 和 node-core test.py ——也包括在docker build内运行的测试(会移除 BuildKit 的日志前缀)。输出按每个测试在多少份采样日志中失败排序,同时列出不同提交和任务;排名第一的问题测试也会列入顶部收益(psf/requests 的实际案例:tests/test_requests.py::TestRequests::test_pyopenssl_redirect)。无法识别的失败会如实说明,而不会猜测——构建错误并不等于不稳定测试。如果不稳定运行的日志没有可识别的格式,则会将其上传的**测试报告制品(JUnit XML / .NET TRX / NUnit3 / TestNG)**作为后备来源(仅当该次运行中的每个失败任务都已确认不稳定时才会使用——真正损坏的同级任务不得被误判为不稳定;按运行归因,另设小节)。需要身份验证,与所有需要下载日志的功能相同。 - 缓存命中率(
--cache-logs N)— API 不会告诉你缓存是否真的命中;只有日志文本中记录了这些信息。此功能会采样 N 份最近任务日志(每份日志各发起一次 API 请求,并轮流覆盖不同任务名称,避免频繁输出日志的矩阵挤占其他任务的份额),并解析actions/cache、setup-go、setup-node等工具输出的缓存标记:精确命中、通过restore-keys部分命中、未命中、下载的兆字节数——按键模式分组,并折叠哈希值(Linux-go-4ae0e4f8… → Linux-go-*)。此外还会统计因“unable to reserve cache”竞争而未能保存的缓存——并发任务悄悄重新构建相同的键。需要身份验证:即使是公共仓库,没有令牌时下载日志也会返回 403。
单次运行深度分析(--run)
“为什么这次运行这么慢?”——将 --run 指向运行 ID、粘贴的运行 URL 或
latest,即可深入剖析这次运行:
- 运行整体耗时瀑布图:展示运行的挂钟时间:排队等待(
·)与执行 (█),按结论着色,让并行空档和 Runner 资源不足一目了然。 - 步骤耗时与工作流自身的历史记录对比——每个作业和步骤都会与同一工作流最近 8 次成功运行的中位数进行比较。结论会指出性能回退项:
⚠ "Install dependencies" in build (3.12, windows-latest): +59s vs its p50 (3.7x slower)。 - 失败的运行会首先说明失败位置(
✗ job "lint" failed at step "golangci-lint")——绝不会因为“完成得很快”而受到称赞。 - 直接在报告中显示失败步骤的日志末尾(已认证的运行):显示失败步骤的最后 20 行——根据步骤自身的时间戳从作业日志中截取,以
##[error]标记为锚点,并删去清理相关的日志——这样无需点击进入 Actions UI,就能看到实际的编译器错误或--- FAIL行。使用--log-tail N调整(设为 0 可关闭)。 - 按名称列出失败测试(已认证的运行):作业日志会通过同一组为
--flaky-logs提供支持的31 种框架提取器 进行处理,因此失败运行的结论会显示✗ job "test (3.12)" failed at step "pytest" — 3 failing tests incl. tests/test_retry.py::test_backoff,而无需你在日志中滚动查找。 按设计,构建和基础设施失败不会提取任何内容——没有可识别的测试失败输出,就不会列出测试名称,也不会猜测。 - 测试报告制品作为后备方案(JUnit XML / TRX / NUnit3 / TestNG)(已认证的运行):
如果失败作业的控制台输出中没有可识别的格式,系统会检查该运行上传的制品,查找 JUnit XML 报告(
pytest --junitxml、surefire、Gradle、jest-junit、ctest --output-junit等)、 .NET TRX 文件(dotnet test --logger trx)、NUnit3 报告 (nunit3-console、dotnet test --logger nunit、Unity 的测试运行器)或 TestNG 原生结果(testng-results.xml——Selenium 风格的自动化测试套件常常只上传这一文件),并从中列出失败测试名称——适用于任何会写入行业标准报告文件的框架,且名称准确无误。结果会在运行级别列出,并注明来源制品,因为制品属于运行,而非某个作业——绝不会错误归因。 - **能够识别重新运行情况:**尝试编号、重新运行的作业与沿用先前尝试结果的作业,以及明确说明的计费影响。
- 遵循常规的可信度规则:可比较的成功运行少于 3 次时,会附注并取消比较,而不会伪造比较结果。进行中的运行会显示“截至目前”,而不会给出结论。
也支持 --json 和 --md(gha-doctor --repo owner/name --run latest --md
的输出可直接粘贴到故障事件议题中)。
PR 深度分析(--pr)
“我的 PR 上的 CI 为什么失败了?”——执行相同的深度分析,但从 PR 编号(或 #N,或粘贴的 PR URL)开始——URL 还会固定仓库:
gha-doctor --repo owner/name --pr 123
它会获取 PR 头部提交上的每次工作流运行,显示各自的结果、耗时和尝试次数,然后:
- 反馈时间——提交等待明确 CI 信号的时长:直到首次失败(失败一出现就可采取行动)的时间,或所有检查通过时直到全部变绿的时间。正在进行的运行会标记为进行中,不会判定结果。
- 重新运行迹象——尝试次数大于 1 的运行意味着有人点击了重新运行;如果这样一来结果由红变绿,你很可能遇到了不稳定检查。
- 深入分析最新一次失败运行的完整过程——瀑布图、步骤回归、明确列出的失败测试、失败步骤日志末尾内容——与
--run展示的内容完全相同,无需先查找运行 ID。 - 一如既往,诚实呈现:包含多个提交的 PR 只分析头部提交;没有运行记录的 PR 会明确说明,而不是呈现空报告。
健康评分与徽章
所有测量结果会汇总为 0–100 分的 CI 健康评分(A+–F),并逐项列出,让你清楚了解每项扣分情况:
Health score
F (54/100, run history only)
✗ success rate −17.5 72% of 100 sampled runs succeeded
! queue time −0.3 average 6 s waiting for a runner
! flakiness −5 1 job failed AND passed on the same commit
! wasted minutes −4.1 8% of sampled compute minutes went to failed runs or retries
✗ cache pressure −5 cache storage at 101% of the 10 GB limit (evictions likely)
--badge health.svg 会生成一个 shields 风格的 SVG 评分图,可放入你的 README;一个小型定时工作流还能让它保持最新。
--score-history scores.jsonl 会将每次运行记录到可提交的 JSONL 文件中,并打印与上次相比的变化(Δ +7 since 2026-07-22 (B 84 → A 91)),包括哪些组成项有所改善或退步。两个标志一起使用时,徽章会显示近期评分的迷你趋势图——一眼就能看到趋势,并与构建徽章并列。权重、公式、趋势跟踪和徽章工作流详见 docs/score.md。
Prometheus / Grafana(--prom)
gha-doctor --prom ci-health.prom # alongside the normal report
--prom 会以 Prometheus 文本展示格式写入所有测量得到的汇总数据——健康评分、按严重程度划分的问题、各工作流的成功率和 p50/p95 耗时、排队时间、浪费和向上取整后的计算资源消耗(秒数和美元)、不稳定任务、僵尸定时任务、相对于 10 GB 限制的缓存大小、被取代运行造成的浪费、PR 反馈时间。按计划运行后,CI 健康状况就能成为 Grafana 仪表板的一部分,展示真实历史数据,而非某个时间点的报告。文档中附带可直接使用的仪表板(健康评分仪表盘、浪费量和耗时趋势、与 10 GB 限制对比的缓存、按仓库筛选的模板变量)——详见 docs/grafana.md,该仪表板已通过实时 Grafana + Prometheus 堆栈验证。
两种简单的接入方式:
-
Textfile collector(自托管运行器或任何装有 node_exporter 的机器):将文件写入收集器目录——
gha-doctor --prom /var/lib/node_exporter/textfile/gha-doctor.prom。 -
Pushgateway(托管运行器):由定时工作流推送导出数据——
- run: | gha-doctor --fail-on never --prom metrics.prom curl --data-binary @metrics.prom \ https://pushgateway.example.com/metrics/job/gha-doctor/instance/${{ github.repository_owner }}-${{ github.event.repository.name }}
同样坚持如实呈现:运行未测量的内容完全不会生成任何时间序列(仪表板上的空缺才是真相;填充为零的时间序列则是谎言),而测量得到的零值——例如采样时间段内没有不稳定任务——则是真正的 0。每个值都是描述采样时间段的仪表;gha_doctor_sample_since_timestamp_seconds 表示该时间段回溯到何时,而 gha_doctor_last_run_timestamp_seconds 则用于检测数据是否过时。
全组织分诊(--org)
gha-doctor --org yourorg # 20 most recently pushed repos, 100 runs each
gha-doctor --org yourorg --max-repos 50 --md # bigger fleet, Markdown for an issue
在一个屏幕上查看整个组织:按仓库统计运行量、失败率、p50/p95 时长,以及每 30 天的预估墙钟运行分钟数,并按消耗最多的仓库排序。用户账号也适用,并会自动跳过分叉仓库和已归档仓库。它还会指出整个集群中的僵尸定时任务——其采样运行结果连续失败的计划工作流(检查条件与仓库级检查相同),直接从已获取的运行样本中识别,不会产生额外 API 成本。失效的 lock-issues 机器人和损坏的夜间构建会在数月间悄悄抬高仓库失败率;集群视图会直接指出它们。这里不会预估每月 $ 成本——组织扫描不会获取作业数据,因此价格只能靠猜;使用 --repo 深入查看可计费的消耗。
这是刻意设计成低成本的:每个仓库只需一次 API 请求(仅运行级数据),因此拥有 50 个仓库的组织只需约 51 次请求,而不是数千次。这也意味着显示的分钟数是每次运行的墙钟时间,而非可计费的作业分钟数——并行作业会分别全额计费——所以请将其视为分诊视图:找出最突出的仓库,然后使用 --repo org/name 深入查看精确的逐作业计费、易出错作业和缓存健康状况。
集群卡片(--org + --svg)
gha-doctor --org yourorg --svg fleet.svg
将集群表格写入独立的 SVG 卡片,可嵌入组织个人资料 README 或仪表板中(最繁忙的 12 个仓库 + 汇总其余仓库;可像评分徽章那样通过定时工作流重新生成):
对比
| actionlint | zizmor | gha-doctor | |
|---|---|---|---|
| 侧重点 | 正确性 | 安全性 | 速度 / 成本 / 可靠性 |
| 静态工作流检查 | ✅ | ✅ | ✅(性能和成本规则) |
| 使用你的运行历史记录 | ❌ | ❌ | ✅ |
| 易出错作业检测 | ❌ | ❌ | ✅ |
| 浪费分钟数估算 | ❌ | ❌ | ✅ |
$ 成本估算(含进位) | ❌ | ❌ | ✅ |
| 缓存限制 / 陈旧缓存检查 | ❌ | ❌ | ✅ |
| 缓存命中率测量(基于日志) | ❌ | ❌ | ✅ |
| 构件存储 / 保留期限检查 | ❌ | ❌ | ✅ |
它们可以组合使用:三个工具都运行。有关详细且客观的分析——包括这三个工具的重叠之处——请参阅 gha-doctor 与 actionlint、zizmor 的对比。
许可证
MIT © Linnea Bakshi
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。
