永远别等「最新的」CI run
触发 workflow 后再取「最新一次绿灯」,你可能等到旧 run、并发 run,甚至一次完全不同的发布。真正可靠的链路,要把同一个不可变身份从源码意图一路带到 workflow run,再用公开版本 stamp 证明结果。
问题
发布脚本触发 workflow,去 GitHub 取「最新一次」run,等它结束,看到绿灯,然后宣布成功。代码很短:
gh workflow run deploy-selfhost.yml --ref main
run_id=$(gh run list --workflow deploy-selfhost.yml --limit 1 --json databaseId --jq '.[0].databaseId')
gh run watch "$run_id" --exit-status
坑就藏在「最新」两个字里。latest 是共享队列上不断变化的属性,不是你刚刚发起的那次操作的身份。
同一个 commit 可能已有旧 run;另一个操作者可能插在两条命令之间 dispatch;API 也可能还没列出刚创建的 run。每种情况下,脚本都可能盯着一条真实 workflow、拿到一个真实绿灯,却回答了错误的问题。
只有在等待开始前就确定身份,并把等待绑在那个身份上,等待才安全。
先冻结源码
「发布 main」不是身份,因为 main 会移动。我们的发布链先把目标源码解析成完整 commit SHA。人工 dispatch 会把它作为 expected_sha 带进 workflow;如果 GitHub 实际准备构建的是另一个 commit,workflow 会拒绝继续。
这道检查必须放在远端 worker,不能只由调用方事前检查。调用方刚核对完分支,仍可能在 --ref main 真正解析前输掉竞态。让 .github/workflows/deploy-selfhost.yml 自己重查,才把「我们想发布哪个 SHA」从注释变成可执行边界。
规则很简单:批准前先解析可变名字,再让真正执行工作的系统拒绝任何不同的不可变 id。
用集合差抓住这一次 run
GitHub CLI 人工 dispatch 后不会直接返回新 run id。scripts/hotfix-deploy.sh 会自己补上这条关联:dispatch 前,按 workflow、branch、event 类型和完整 commit 列出一组 run;dispatch 一次;再列同一组过滤结果,然后减去旧 id:
before = matching_runs(workflow, branch, event, commit)
dispatch(expected_sha=commit)
repeat until visible:
after = matching_runs(workflow, branch, event, commit)
created = after - before
require created.count == 1
run_id = created.only_item
只按 commit 不够,因为自动与人工 run 可能共享它;只按 event 不够,因为可能已有多条人工 run;拿最新一条,仍是在读共享队列。集合差问的是唯一重要的问题:哪个符合条件的 id,是因为这一次 dispatch 才出现的?
开始等待前,脚本会重读这条 run,核对源码 SHA 与 event 类型;run 进入终态后再核对一次。要消费的结果,必须始终属于那次已经获准的操作。
执行绿了,不等于公开结果已生效
绑对 workflow 只解决相关性,还没有证明交付。job 可以成功结束,而路由、缓存或另一个耦合表面仍暴露上一版。
精确 run 成功后,hotfix 通道会检查 /readyz,再读取两份公开回执:App 的 build-stamp.txt 与官网 apex 的 release-source.json。两边必须同时声明目标 SHA 和同一个 release id;只要不一致,workflow success 就不能向上取整成线上发布成功。
于是一次发布有两张分开的证明:编排系统给出执行回执,部署后的系统给出结果回执。前者证明跑过什么;只有后者能证明用户现在真正访问到什么。
可以直接复用的清单
- 冻结意图。把 branch、tag 或 label 解析成不可变 id。
- 把 id 带进 worker。让远端自己拒绝不同源码。
- 抓住新建的 attempt。API 能返回 id 就直接用;不能,就对严格过滤后的前后集合做差,并要求恰好一个新 id。
- 消费结果前重新绑定。等待前后都重读 attempt,核对源码和触发类型。
- 另外证明真实效果。向部署后的系统查询不可变版本 stamp,并要求耦合表面全部一致。
- 专测「错的成功」。旧绿灯、并发 dispatch、列表延迟、公开 stamp 不一致。
「等最新一次」看似省了一行,实际删掉了后续自动化最需要的事实:我们说的到底是哪一次操作?让同一个身份从意图一路活到公开结果,GREEN 才终于代表你以为的那件事。