异常场景设计
本文档覆盖 pipeline.sh 在边界情况下的行为设计,确保流水线在任何异常状态下不破坏仓库状态。
1. PR 合并冲突
场景
NPC 提交的代码与当前仓库有冲突,自动合并失败。
行为
python
# pipeline.sh 中的 merge_open_prs()
result = curl -X PUT "$API/$org/$repo/-/pulls/$num/merge"
# 如果 merged 返回 False(含冲突场景)
# → 记录错误到日志
# → 跳过此 PR(不强制合并)
# → 在终端输出"⚠️ PR #N 存在冲突,请手动处理"
# → 继续流水线,不阻塞其他工位手动处理
bash
# 1. 克隆仓库
git clone https://cnb.cool/$org/$repo.git
# 2. 手动解决冲突
git merge $source_branch
# ... 解决冲突 ...
git add -A && git commit -m "fix: resolve merge conflict"
git push
# 3. 通知 pipeline.sh 继续
# 重新运行会检测到断点并跳过已完成的工位
bash pipeline.sh -p $PROJECT_NAME设计原则
- 永不强制覆盖:merge 失败时不 force push
- 永不丢弃代码:不删除有冲突的 PR 分支
- 可手动恢复:冲突后停留在可知状态
2. NPC 重复触发
场景
网络抖动导致 wait_for_npc 重试时,NPC 被重复触发(同一 Issue 被多次评论唤醒)。
行为
python
# pipeline.sh 中的 wait_for_npc()
comments = curl "$API/$org/$repo/-/issues/$issue_number/comments"
for c in comments:
if any(keyword in c.body for keyword in ["已完成","done","PR 已创建"]):
# NPC 已声称完成,停止等待
return 0
# 不判断 NPC 是否"正在执行",只判断是否"已完成"安全设计
| 机制 | 说明 |
|---|---|
| 幂等检查 | NPC 完成后会推代码 + 创建 PR,PR 存在即视为完成 |
| 评论判断 | 只依赖 NPC 的"已完成"声明,不假定 NPC 行为 |
| 超时保护 | 超过 timeout_minutes 后停止催促,避免无限唤醒 |
原理
CNB 平台允许 NPC 并发,但多个 NPC 处理同一 Issue 会各自创建 PR。pipeline.sh 只关心"至少一个 PR 被创建",不要求"只有一个 NPC 在工作"。
3. 流水线中途失败
场景
流水线在第 3 个工位(前端)运行时,网络中断、机器关机或进程被 kill。
行为设计
状态文件: /tmp/ai-pipeline/{project}-state.json
{
"project": "my-app",
"current_stage": "frontend",
"completed": ["prd:done", "ui:done"],
"started_at": "2026-05-17T06:00:00Z"
}| 状态 | 断点续跑行为 |
|---|---|
{stage}:done | 跳过,进入下一工位 |
{stage}:timeout | 跳过,进入下一工位 |
{stage}:skip | 跳过,进入下一工位 |
current_stage (未记录状态) | 重试此工位(重新创建 Issue) |
设计原则
- 可重复性:重新运行
bash pipeline.sh -p my-project会自动恢复 - 幂等性:检查仓库是否已存在,Issue 是否已创建
- 安全性:不删除已有数据,只追加
4. 并发流水线
场景
同一台机器上同时为两个项目运行流水线。
行为
bash
# 状态文件按项目名隔离
/tmp/ai-pipeline/project-a-state.json
/tmp/ai-pipeline/project-b-state.json| 资源 | 隔离方式 |
|---|---|
| 状态文件 | 按 {project-name}-state.json 隔离 |
| 仓库操作 | 按 org/repo 隔离,不同项目不同仓库 |
| Issue/PR | 按 repo 隔离,互不影响 |
| 临时目录 | 每次 clone 用 mktemp -d 创建独立目录 |
风险
- 同一台机器的 CNB Token 被共享(多次调用的速率限制)
- 终端输出可能交错(建议分终端运行)
建议
bash
# 终端 1
bash pipeline.sh -p project-a
# 终端 2
bash pipeline.sh -p project-b5. 组织保护拒绝 Transfer
场景
group_protection: 1 导致 transfer_repo 失败(errcode 9)。
行为
python
# lib/transfer.sh 中的 transfer_repo()
1. curl -X PUT "$API/$org/-/settings" -d '{"group_protection":0}'
2. 如果返回 200 → 保护已关闭
3. 如果返回 403/其他 → 可能是权限不足
4. 如果后续 transfer 仍报 errcode 9 → 中止流水线,提示手动关闭保护手动补救
bash
# 在 CNB Web 控制台关闭
# 或通过 API:
curl -X PUT https://api.cnb.cool/$org/-/settings \
-H "Authorization: Bearer $CNB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"group_protection":0}'
# 然后重新运行 pipeline.sh(断点续跑)6. 资源残留
场景
流水线跑了一半后放弃,留下半成品仓库。
清理脚本
bash
# cleanup.sh — 清理流水线创建的仓库
# 用法: bash cleanup.sh <project_name>
PROJECT=$1
if [ -z "$PROJECT" ]; then
echo "用法: bash cleanup.sh <project_name>"
exit 1
fi
for org in cnbdocs cnbuu cnbvv cnbmm cnbyy cnbnn; do
echo "清理 $org/$PROJECT ..."
curl -X DELETE "https://api.cnb.cool/$org/$PROJECT" \
-H "Authorization: Bearer $CNB_TOKEN"
done
rm -f "/tmp/ai-pipeline/${PROJECT}-state.json"
echo "✅ 清理完成"7. Token 过期
场景
流水线运行途中 CNB_TOKEN 过期。
处理
python
# pipeline.sh 中每个 API 调用后检查
result = curl -s -w "\n%{http_code}" -H "Authorization: Bearer $CNB_TOKEN" ...
http_code = result.split("\n")[-1]
if http_code == "401":
print("❌ Token 已过期,请更新 CNB_TOKEN 后重新运行")
save_state(current_stage, completed) # 保存当前进度
exit 1
# 重新设置 Token 后:
# bash pipeline.sh -p $PROJECT → 断点续跑