OpenHuman 的 `/ship-and-babysit` 命令:一条从 Commit 到 PR 全绿自动盯梢的交付流水线
/ship-and-babysit 是 OpenHuman 仓库中一条 Claude Code 斜杠命令(定义于 .claude/commands/ship-and-babysit.md),它把「提交代码 → 推送到 fork → 向上游开 PR → 每 5 分钟轮询 CI 与 CodeRabbit 评论并修复问题,直到全部变绿」这一完整交付闭环固化为一个可重复执行的流程。读完后,你将掌握 OpenHuman 的 fork-only 协作模型、PR 模板与检查清单的机器校验规则、diff-cover 80% 变更行覆盖率门槛的底层实现,以及如何用 gh CLI 与 GitHub GraphQL API 完成 CI 状态抓取、CodeRabbit 评论线程的回复与解决等实战操作。
整体流程:四个阶段与 fork-only 原则
该命令要求按顺序执行四个阶段:Phase 1 Commit → Phase 2 Push → Phase 3 Open PR → Phase 4 Babysit loop(约每 5 分钟一轮),每个阶段切换时只向用户输出一句简短说明,保持对话简洁。
仓库事实(来自 CLAUDE.md 第 1269-1280 行的贡献章节)构成了整个流程的安全边界:
- Upstream 是
tinyhumansai/openhuman(不是 fork),PR 目标是main分支。 - 推送一律发到
origin——即用户自己的 fork;upstream视为 fetch-only,绝不向它推送分支。CLAUDE.md 中给出的推荐 remote 配置为:
origin git@github.com:<your-username>/openhuman.git # push here
upstream git@github.com:tinyhumansai/openhuman.git # fetch-only
- PR 用
--head <fork-owner>:<branch>打开,目标仓库为tinyhumansai/openhuman:main。 - 功能开发必须先有对应的 E2E 覆盖,这是「ship」的前置条件。
流程是 fork-only 的:origin 必须是用户的 fork。如果解析出 origin 指向 tinyhumansai(上游组织本身),必须停下来让用户添加 fork remote,绝不能把分支推到上游仓库。
流程开始时先一次性解析 fork owner 并在整个流程中复用:
FORK_OWNER=$(git remote get-url origin | sed -E 's#.*://[^/]+(\.git)?$#\1#')
这条 sed 用 [:/] 同时兼容 SSH 形式(git@github.com:owner/repo.git)和 HTTPS 形式(https://github.com/owner/repo)的 remote URL,从最后一级路径中提取用户名。
Phase 1 — Commit:理解变更、遵循提交规范
提交阶段的第一步是并行执行 git status、git diff(含已暂存与未暂存)以及最近的 git log,目的是同时了解待提交内容和仓库既有的 commit message 风格。
命令定义了若干明确的分支逻辑与禁令:
- 快速通道:如果没有可提交的变更、分支已经推送、且 PR 已经存在,则直接跳到 Phase 4(盯梢循环),避免无意义的重复提交。
- 暂存与提交:存在未提交变更时,先暂存相关文件(明确排除 secrets、大二进制文件、
.env),然后用 conventional prefix(feat:、fix:、refactor:、chore:、docs:、test:)创建提交,提交信息使用 HEREDOC 写入。 - Hook 纪律:绝不允许对自己造成的 hook 失败使用
--no-verify绕过。如果 hook 因为自己的改动而失败,正确做法是修复根本问题再创建新提交——不要amend已推送的提交。
Feature E2E 规则:ship 之前的覆盖底线
文档单独设立了一节「Feature E2E rule」,规定了不同类别功能变更所要求的 E2E 覆盖位置,这些路径在当前仓库中均可逐一验证:
- 核心、领域、持久化、CLI、JSON-RPC 功能变更:需要 Rust E2E 覆盖,落在
tests/*_e2e.rs;新的或变更的 RPC 面通常归属 tests/json_rpc_e2e.rs。 - 前端用户流程:需要 Playwright E2E 覆盖,落在
app/test/e2e/specs/*.spec.ts。仓库当前有 100+ 个 spec 文件,如auth-access-control.spec.ts、chat-harness-send-stream.spec.ts等(见 app/test/e2e/specs/)。 - Mock 到底:E2E 中必须用 scripts/mock-api-server.mjs、
scripts/mock-api/*或 app/test/e2e/mock-server.ts 把后端调用全部 mock 掉,禁止在 E2E 中访问真实后端服务或第三方 API。 - 单元测试不替代 E2E:单元测试对窄逻辑依然重要,但不能覆盖「新构建的功能」所需的 E2E 证明。
推荐的聚焦化运行命令(均可在当前仓库的 package.json / app/package.json 中找到对应 script 定义):
pnpm test:rust:e2e -- --suite <suite> # 单个 Rust E2E suite
pnpm --filter openhuman-app test:e2e:web:build # 构建 Web E2E 目标
bash app/scripts/e2e-web-session.sh test/e2e/specs/<spec>.spec.ts # 跑单个 Playwright spec
其中 test:rust:e2e 实际映射到 scripts/test-rust-e2e.sh,该脚本头部注释明确说明 --suite <name> 可以多次传入以过滤特定 suite——「the cargo-test counterpart to the Tauri E2E specs」。
Phase 2 — Push:分支命名约定与 pre-push hook 策略
推送阶段有三道关卡:
- 分支名检查:用
git rev-parse --abbrev-ref HEAD确认当前分支符合feat/|fix/|refactor/|chore/|docs/|test/前缀约定,绝不直接推main。若分支名不合规,停下来让用户选择改名或确认偏离——不要自动重命名已推送的分支。 - 推送到
origin:缺少 upstream tracking 时加-u。绝不推upstream,绝不对mainforce-push。 - pre-push hook 的差异化处置(与 CLAUDE.md 中的 on-push-blockers 策略一致):
- 若 hook 失败源于与本次改动无关的既有问题(如
main上你未触碰的代码已经坏了),用--no-verify推送,并在 PR 正文中明确说明; - 若 hook 失败源于自己的改动,修复后重新推送。
- 关键措辞是「Don't ask — just do the right thing and tell the user what you did」:授权 Agent 自行做正确的事,但必须告知用户。
- 若 hook 失败源于与本次改动无关的既有问题(如
Phase 3 — Open PR:模板、检查清单与幂等性
开 PR 前先验证 upstream remote 指向 tinyhumansai/openhuman(缺失时先询问用户再添加)。然后是幂等性检查——确认该分支是否已有 PR:
gh pr list --repo tinyhumansai/openhuman --head <fork-owner>:<branch> --state open --json number,url
- 若已存在 PR:记录
number和url,打印 URL,跳过后续步骤直接带 PR# 进入 Phase 4。 - 若不存在:用
git log main..HEAD检视提交、用git diff main...HEAD检视差异,据此起草标题(<70 字符)与正文。正文必须严格遵循 .github/PULL_REQUEST_TEMPLATE.md。
模板的实际结构为:## Summary(3-6 条,聚焦用户可见或影响架构的变更)、## Problem、## Solution、## Submission Checklist、## Impact、## Related(含 Closes #NNN),以及针对 AI 生成 PR 的 ## AI Authored PR Metadata 区(Linear Issue、Commit & Branch、Validation Run、Parity Contract、Duplicate/Superseded PR Handling 等字段)。
检查清单有一条机器强制的硬规则:每一项必须勾选;不适用的项写 - [x] N/A: <reason>——未勾选的 N/A 项同样会失败。这条规则的落地在 scripts/check-pr-checklist.mjs 及其解析器 scripts/lib/checklist-parser.mjs:
- 解析器用正则
^- \[( |x|X)\] (.*)$逐行识别清单项(跳过代码围栏内的行),用NA_REGEX识别N/A前缀并提取理由; - 只要
totalUnchecked > 0,脚本以退出码 1 结束,并逐条打印未勾选项,附注「N/A items must still be checked with a reason」; - 该脚本由 CI 工作流 .github/workflows/pr-quality.yml 调用(
node scripts/check-pr-checklist.mjs),支持从文件、stdin(-)或PR_BODY环境变量读取 PR 正文,因此本地也能先跑一遍自检。
PR 正文若绕过过一次 pre-push hook,也必须在这里写入说明。创建命令的完整形态:
gh pr create --repo tinyhumansai/openhuman --base main --head <fork-owner>:<branch> \
--title "..." --body "$(cat <<'EOF'
...template-filled body...
EOF
)"
最后一步:按仓库惯例添加 labels,记录 PR 编号与 URL——它们在 Phase 4 中会被反复使用。
Phase 4 — Babysit loop:270 秒节拍、12 次硬上限
这是整个命令最有工程含量的部分。循环通过 ScheduleWakeup 工具以 270 秒(4.5 分钟)为节拍自我唤醒——这个数值被刻意选在「prompt-cache 窗口」之内,且每次唤醒时把同一个 /ship-and-babysit 调用作为 prompt 重新传入,实现状态延续。
硬上限:12 个 tick(约 60 分钟)。 超限后停止循环并询问用户,汇报中必须包含 PR URL、当前 CI 快照与仍未解决的 CodeRabbit 线程。为防止计数器漂移,命令要求维护一个显式 tickCount(每次进入循环 +1,无论本 tick 是否产生提交),并把它写进 ScheduleWakeup 的 reason 字段,例如 "tick 5/12: waiting on CI for PR #1115"——即使某个 tick 什么都没做,计数也可见、可审计。
每个 tick 的第 1 步:抓取 CI 状态
gh pr checks <PR#> --repo tinyhumansai/openhuman --json name,state,link,description
这里藏着两个容易被忽略的实战细节,文档都给出了结论:
-
link字段是 Actions URL 而非 run id(形如…/actions/runs/<id>/job/<jobId>)。提取 run id 要用对尾部斜杠稳健的正则:sed -nE 's#.*/actions/runs/([0-9]+)/.*#\1#p'而按位置取
awk -F/在 URL 带尾部斜杠时是脆弱的。或者干脆跳过 URL 解析:gh run list --repo tinyhumansai/openhuman --branch <branch> --json databaseId --limit 1 --jq '.[0].databaseId' -
区分 Actions 支撑的检查与非 Actions 检查:只有当
link匹配/actions/runs/<id>/时才用gh run view <id> --log-failed --repo tinyhumansai/openhuman拉失败日志;像CodeRabbit这类通过 Checks API 直接上报、没有对应 Actions run 的虚拟检查,必须跳过gh run view,转而基于name/state/description字段加上 review 评论来分析。
修复纪律:定位到根本问题后修代码、提交(conventional prefix)、推 origin——绝不通过跳过 hook 或禁用失败测试来把 CI 变绿。
推送修复前的本地复现命令按失败域划分:
| 失败域 | 本地复现命令 |
|---|---|
| 前端 | pnpm typecheck、pnpm lint、pnpm format:check、pnpm test |
| Rust | cargo check --manifest-path Cargo.toml、cargo check --manifest-path app/src-tauri/Cargo.toml、pnpm test:rust |
| 功能 E2E | pnpm test:rust:e2e -- --suite <suite>(核心/RPC 行为);前端流程则 pnpm --filter openhuman-app test:e2e:web:build + bash app/scripts/e2e-web-session.sh test/e2e/specs/<spec>.spec.ts |
覆盖率门槛:变更行覆盖率 ≥ 80%。文档中引用的 coverage.yml 在当前仓库里对应的实际执行位置是 .github/workflows/ci-lite.yml(.github/PULL_REQUEST_TEMPLATE.md 与 Issue 模板也都指向 ci-lite.yml)。从 ci-lite.yml 第 1328-1363 行可以看到门槛的真实实现:
- 下载所有
lcov-*artifact(Vitest 与 cargo-llvm-cov 各自产出,合并给 diff-cover); - 运行
diff-cover "${LCOV_FILES[@]}" --compare-branch=<base> --fail-under=80 --html-report ... --markdown-report ... --format json:diff-coverage.json; - 一个关键的防御性注释:diff-cover 在「diff 中没有任何带覆盖信息的行」时会以退出码 0 通过——注释提到 PR #5593 曾因此带着 1,643 行未编译代码溜过门槛。所以工作流会解析 JSON 报告中的
total_num_lines:若为 0 则发::warning::提醒「该代码没有被任何 coverage lane 编译过」,而真正判定「文件从未被编译」的硬失败由 scripts/ci/assert-coverage-presence.sh(配合 scripts/ci/coverage-presence-allowlist.txt 白名单)在核心 coverage lane 内完成。这也解释了命令中的一条指引:覆盖率失败时,要为变更行补测试,而不只是补 happy path。
每个 tick 的第 2 步:处理 CodeRabbit 评论
先用 REST 分页拉取两类评论:
gh api repos/tinyhumansai/openhuman/pulls/<PR#>/comments --paginate # review 评论
gh api repos/tinyhumansai/openhuman/issues/<PR#>/comments --paginate # issue 级评论
过滤 coderabbitai / coderabbitai[bot] 作者。然后对每条未解决的 CodeRabbit 建议做判定:
-
建议正确且在本 PR 范围内:读它引用的文件/行,直接修复,commit 后推
origin; -
建议错误或超出范围:先在既有线程内回复再解决。文档特别澄清了一个 API 语义陷阱:
gh api repos/tinyhumansai/openhuman/pulls/comments/<comment_id>/replies \ -X POST \ -f body='**Dismissed:** <reason>'其中
<comment_id>是顶层 review comment 的 id;若改用POST /pulls/<PR#>/reviews会创建一条全新的 review 线程,而不是在当前线程下回复,这会污染 PR 的评论结构。
解决线程要走 GraphQL API:
gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -f id=<threadId>
列出 thread ID 必须做分页——这是文档中另一处易错点:reviewThreads(first:100) 每页最多 100 条,要循环 pageInfo.hasNextPage / endCursor,把 endCursor 回填为下一页的 $cursor 直到耗尽,否则第 1 页之后的线程会静默逃过退出条件:
gh api graphql -f query='query($owner:String!,$repo:String!,$num:Int!,$cursor:String){repository(owner:$owner,name:$repo){pullRequest(number:$num){reviewThreads(first:100, after:$cursor){pageInfo{hasNextPage endCursor} nodes{id isResolved comments(first:1){nodes{author{login} body}}}}}}}' \
-F owner=tinyhumansai -F repo=openhuman -F num=<PR#> -F cursor=
每个 tick 的第 3 步:退出条件(三条同时成立)
- 所有必需检查均为
SUCCESS。PENDING会让循环继续,没有例外——CI 还在跑时不许宣称「green」; - 没有未解决的 CodeRabbit review 线程;
- 自上上个 tick 以来没有新的、要求修改的 CodeRabbit issue 评论。判定方式利用了 GitHub issue-comment id 的单调性:记住上一个 tick 见过的最大 CodeRabbit issue-comment id,本 tick 只把严格大于该标记的 id 视为新评论。
三条同时成立时,不要再调用 ScheduleWakeup,直接返回一行最终摘要(含 PR URL 与当前状态)。否则以 delaySeconds: 270、prompt: "/ship-and-babysit"、具体的 reason(如 "waiting on CI for PR #123" 或 "applied 2 CodeRabbit fixes, re-checking")调用 ScheduleWakeup 进入下一拍。
Guardrails:五条不可逾越的红线
命令末尾的 Guardrails 是整个流程的安全契约,值得逐条对照:
- 永不推
upstream(tinyhumansai/openhuman)——只推origin(用户 fork),upstream 视为 fetch-only; - 永不对
mainforce-push,永不 amend 已推送的提交; - 永不用
--no-verify绕过自己改动导致的 hook 失败。唯一被授权的绕过场景是「pre-push hook 因既有无关损坏而失败」,且必须在 PR 正文中说明; - 永不在真正处理问题(或回复了有理有据的 Dismissed)之前解决 CodeRabbit 线程;
- 遇到需要人类输入的阻塞——认证失败、含糊的 CodeRabbit 建议、互相矛盾的反馈、merge 冲突、vendored
tauri-cli缺失——停下来问用户,而不是猜; - 不要合并 PR。流程的终点是「green and clean」,merge 决策留给人。
小结:这条命令把哪些工程判断固化了下来
/ship-and-babysit 的价值不在命令本身,而在于它把大量「只有踩过坑才知道」的判断写死成了规则:fork-only 的 remote 纪律、gh pr checks 返回 Actions URL 而非 run id 的解析陷阱、review 回复与新建 review 线程的 API 语义差异、GraphQL 分页的静默漏读、diff-cover 零行通过假象背后的 coverage-presence 双保险、以及 CodeRabbit 评论 id 单调性用于增量检测。配合 .github/PULL_REQUEST_TEMPLATE.md 的机器校验(scripts/check-pr-checklist.mjs)、.github/workflows/ci-lite.yml 的 80% 变更行覆盖率门槛与 app/test/e2e/specs/ 的 Playwright 覆盖要求,它构成了一套「提交即可信、PR 即自解释、绿了即干净」的自动化交付管线,也是把 Agent 当作交付值班员时最需要的约束集。
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