--- name: "github-actions-pat" description: "用 Personal Access Token 通过 GitHub REST API 分析 Actions CI 的失败 run/job/step、拉取日志、轮询运行状态、做 PR code review,并驱动 fix → push → watch → iterate 的闭环。Token 只从 GITHUB_TOKEN 环境变量读取,不落盘、不回显。适合在 bifrost remote / CI 调度器 / 无头环境里跑。仓库锁定在 AGENTS.md 或调用处通过 GH_REPO 显式指定。" --- # GitHub Actions PAT Inspector 把"用 PAT 分析 GitHub Actions CI"这条路径固化成可复用脚本。本 skill 解决的典型场景: 1. Agent 在 **bifrost remote** 目标机或 caller 上跑,**必须**走 REST API 自动化,不走浏览器 cookie 2. 需要自动化「修 → push → 盯 CI → 失败继续修」的闭环 3. 需要按 run-id / PR / sha / branch 精确定位失败并拉 job log 4. 需要做 PR diff review(生成 markdown 或直接 POST) **auth 唯一路径:PAT 从 `GITHUB_TOKEN` 环境变量读取**。不支持、不回退到 cookie / OAuth device flow / SSH / gh CLI 登录等其他方式。需要 agent 自主跑的 CI 分析、PR review、fix-push-watch 循环,全部用本 skill。 ## 授权契约(唯一输入:`GITHUB_TOKEN` 环境变量) - 本 skill 只从环境变量 `GITHUB_TOKEN` 读 token。**不落盘、不写日志、不回显原文。** - 读取方式推荐(适配用户 shell 配置): ```bash # macOS / zsh 用户:交互式 shell 才会加载 ~/.zshrc zsh -ic 'python3 scripts/gh_ci.py run ' # bash 用户 bash -lc 'python3 scripts/gh_ci.py run ' # 或在脚本运行前已 export GITHUB_TOKEN 的环境 python3 scripts/gh_ci.py run ``` - 未设置 → 脚本 `exit 2`,提示 `ERROR: set GITHUB_TOKEN before running this skill`。 - 推荐 scope:`repo` + `actions:read`(只读分析)。`--post` 发 review 时额外需要 `pull_requests:write`。 - 首选 **fine-grained PAT** 并把 repo 限定到目标仓库,爆炸半径最小。 ## 仓库定位策略 脚本默认读环境变量 `GH_REPO`(格式 `owner/repo`),未设置时回退到本仓库。Agent 可在调用前写入: ```bash export GH_REPO=bifrost-proxy/bifrost python3 scripts/gh_ci.py pr 567 ``` 或者在 AGENTS.md 顶部的仓库元信息中固化。 ## 目录结构 ``` scripts/ ├── common.py # token/http/分页/日志切片/归因 ├── gh_ci.py # run / pr / sha / branch / regression ├── gh_review.py # PR metadata + diff + 分层建议 + 可选 --post └── poll_run.py # 轮询一个 run 直到完成 references/ └── pitfalls.md # GitHub API 坑点清单(job log 302、Accept 415、system proxy MITM) ``` ## 典型调用 ### (A) CI 错误定位 ```bash # 按 run-id 分析 python3 scripts/gh_ci.py run 25269751068 # 按 PR 号找最近一次 failed python3 scripts/gh_ci.py pr 567 # 按 commit sha 找 python3 scripts/gh_ci.py sha a96a4257 # 按分支找最近一次 failed python3 scripts/gh_ci.py branch feat/agent --only-failed # 与上一次 green 做 regression 对比(给出 compare URL + 提交区间) python3 scripts/gh_ci.py regression 25269751068 ``` 脚本行为: 1. `GET /repos/{owner}/{repo}/actions/runs/...` 拉 run 元数据 2. `GET .../jobs` 筛出 `conclusion=failure` 3. 对每个 failed job:`GET .../jobs/{id}/logs` → **自动处理 302 到 Azure Blob signed URL**(不能带 Authorization) 4. 日志末尾 500 行扫关键词(`error:`、`FAIL`、`panicked`、`##[error]`、`thread '...' panicked`、`test result: FAILED` 等),抽 ±20 行上下文 5. 输出结构化 markdown:失败 job / step / URL / 根因桶(compile / test / lint / fmt / timeout / network / OOM)/ 关键日志片段 / 本地复现命令 ### (B) 轮询 run 到完成(fix-push-watch 循环用) **默认用 `watch_jobs.py`(fail-fast 模式)—— 只要有任何 job 先失败就立即退出并打印归因,不必等慢的 Windows/macOS bundle 跑完。** ```bash # 推荐:fail-fast 看护。任一 job 失败立即 exit 2 并打印归因 markdown POLL_SEC=20 MAX_WAIT_SEC=3600 python3 scripts/watch_jobs.py # 只有在明确需要"等全部 job 跑完"(例如最终合入前的全绿确认)时才用 poll_run.py POLL_SEC=45 MAX_WAIT_SEC=1800 python3 scripts/poll_run.py # exit 0 = success, 2 = failure, 3 = timeout ``` `watch_jobs.py` 的退出语义: - `0` → run 内所有 job 均 `conclusion=success` - `2` → 至少一个 job 进入 terminal-bad(failure / cancelled / timed_out / action_required / startup_failure);stdout 输出首个失败 job 的 name / step / root-cause bucket / 日志片段 - `3` → 超出 `MAX_WAIT_SEC` 仍有 job 没结束 典型闭环(agent 要这么跑,不要反复问用户,**必须 fail-fast**): ```bash # 1) 修代码 # 2) 本地最小验证:cargo fmt + cargo clippy -D warnings + 相关测试 # 3) push git push origin feat/agent # 4) 找到新 run(注意 --any-status,否则 queued/in_progress 会被过滤掉) python3 scripts/gh_ci.py branch feat/agent --any-status # 5) fail-fast 看护:任一 job 失败就立刻退出并打印归因 python3 scripts/watch_jobs.py # 6) exit 2 → 按 stdout 里的 root-cause bucket + 日志片段立刻回到 (1) 修; # exit 0 → 全绿,汇报用户; # exit 3 → 用 gh_ci.py run 查当前状态,延长 MAX_WAIT_SEC 后再 watch。 ``` > ⚠️ **严禁先 `poll_run.py` 等到"整个 run 完成"再看结果**。Windows / macOS bundle 这类长尾 job 经常要 20 分钟以上,早期失败的 Unit Tests / E2E 如果等到最后才处理,会把修复时间从"立即"拖成"半小时后"。用户明确要求"遇到异常就开始修复"。 ### (C) PR code review ```bash # 只生成 markdown(默认,不发) python3 scripts/gh_review.py 123 # 聚焦某些路径 python3 scripts/gh_review.py 123 --focus 'crates/agent/**' # 限制 diff 上下文 python3 scripts/gh_review.py 123 --max-diff-lines 4000 # 显式 post(需要 pull_requests:write) python3 scripts/gh_review.py 123 --post --event REQUEST_CHANGES ``` 默认**只生成 markdown 不发**;只有用户明确说"发出去 / post it / go ahead" 时才加 `--post`。 ## 已知坑点(踩过的 ⚠️) 1. **Azure Blob signed URL 不能带 Authorization** GitHub 的 `/actions/jobs/{id}/logs` 返回 302 指向 `*.blob.core.windows.net` 签名 URL。如果 HTTP client 自动跟随 redirect 并继续带 `Authorization: Bearer ...`,Azure 会 `401 InvalidAuthenticationInfo`。解决:手动拦截 redirect,二次请求移除 Authorization(本 skill 的 `_fetch_job_log` 已这样做)。 2. **Accept 头不能写 `text/plain`** `jobs/{id}/logs` 端点会返回 `415 Unsupported 'Accept' header`。使用默认 `application/vnd.github+json`(由服务端 302 到签名 URL,body 是纯文本,直接 decode)。 3. **system proxy 会导致 SSL 证书 MITM 失败** 如果用户本机挂了代理(包括 bifrost 自己),Python 的 `urllib` 可能因 `Missing Authority Key Identifier` 而 `CERTIFICATE_VERIFY_FAILED`。跑脚本时 **主动清掉代理环境变量**: ```bash NO_PROXY=api.github.com,github.com,*.blob.core.windows.net \ HTTPS_PROXY= HTTP_PROXY= ALL_PROXY= https_proxy= http_proxy= all_proxy= \ python3 scripts/gh_ci.py run ``` 4. **bifrost remote exec 的 quoting 噩梦** 在 bifrost remote 上跑 Python 单行命令,`\"` 反斜杠层级会叠 3 层。**把探针脚本写成文件后上传再执行**(用 `bifrost remote file write` + `bifrost remote exec python3