Skip to content

异常场景设计

本文档覆盖 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-b

5. 组织保护拒绝 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 → 断点续跑

由云锦鸿维护