用途
post-merge 是自主部署流水线的第 1 阶段(Stage 1)。它在 PR 合并之后运行:读取 diff,判断部署是自动还是手动,必要时执行手动部署,然后派发一个 deployment-checker 任务,轮询直到服务上线。
触发方式
- 自动:当 PR 合并到默认分支时,由 event-rules 自动触发。
- 手动:
/post-merge PR_NUMBER org/repo
调用示例:
/post-merge 42 fellowship-dev/pylot
参数:
PR_NUMBER=$1 # PR number
REPO=$2 # org/repo
运行步骤(Runbook)
Step 0:去重门(Dedup Gate)
检查是否已为该 PR 派发过 deployment-checker 任务,若已存在则直接以成功状态退出:
PR=$1
REPO=$2
# Check if deployment-checker job already dispatched for this PR
EXISTING=$(ls ${PYLOT_DISPATCH_DIR:-$HOME/.local/share/pylot/missions}/pending/ \
${PYLOT_DISPATCH_DIR:-$HOME/.local/share/pylot/missions}/running/ 2>/dev/null \
| grep "deploy-check-${PR}-" || true)
if [ -n "$EXISTING" ]; then
echo "[post-merge] outcome=\"already dispatched — deploy-checker job exists\" status=success"
exit 0
fi
Step 1:收集 PR 上下文
获取 PR 元数据(编号、标题、合并时间、基础分支、头分支、变更文件、URL、合并提交):
# Fetch PR metadata
PR_DATA=$(gh pr view $PR --repo $REPO --json number,title,mergedAt,baseRefName,headRefName,files,url,mergeCommit)
PR_TITLE=$(echo "$PR_DATA" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['title'])")
PR_SHA=$(echo "$PR_DATA" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('mergeCommit',{}).get('oid','') or '')" 2>/dev/null || true)
CHANGED_FILES=$(echo "$PR_DATA" | python3 -c "import json,sys; d=json.load(sys.stdin); print('\n'.join(f['path'] for f in d['files']))")
BASE_BRANCH=$(echo "$PR_DATA" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['baseRefName'])")
echo "PR: $PR — $PR_TITLE"
echo "SHA: $PR_SHA"
echo "Base: $BASE_BRANCH"
echo "Files changed:"
echo "$CHANGED_FILES"
Step 2:读取团队部署配置
读取团队的 CLAUDE.md,确定该仓库配置的部署方式。查找 deploy: 段:
# Example in crew/infra/CLAUDE.md or team CLAUDE.md:
deploy:
method: auto-pull # auto-pull | github-actions | manual
health_url: "http://localhost:1337/_health"
timeout_minutes: 5
checker_script: "scripts/deployment-checker-pylot.sh"
expected_sha_field: "sha" # JSON field in health response containing deployed sha
部署方式决策树:
| Method | Action |
|---|---|
| auto-pull | 部署是自动的。跳过 Step 3 和 Step 4(auto-deploy.sh 负责 deploy-check)。 |
| github-actions | 部署由合并触发。等待约 2 分钟,然后执行 Step 4。 |
| manual | CTO 现在运行部署命令(Step 3),然后执行 Step 4。 |
如果未找到 deploy 配置,发出警告并退出——不要猜测。
Step 3:手动部署(当 method = manual 时)
仅在 method: manual 时执行。从团队配置中读取部署命令:
# Example: manual deploy for a Fly.io app
cd /path/to/repo && fly deploy --remote-only
# Example: manual ECR push
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
docker build -t $IMAGE . && docker push $IMAGE
记录所执行的命令及其输出。
Step 4:派发 deployment-checker 任务
如果 method: auto-pull,跳过此步骤。auto-pull 仓库的 deploy-check 由 auto-deploy.sh 在执行器重启后派发——在此处派发会造成先有鸡还是先有蛋的死锁(deploy-checker 阻塞队列,而队列又阻塞了它正在等待的部署)。
对于其他部署方式(github-actions、manual),派发一个新任务:
if [ "$DEPLOY_METHOD" != "auto-pull" ]; then
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
curl -sS -X POST \
-H "Authorization: Bearer $(grep '^PYLOT_DISPATCH_TOKEN=' $HOME/projects/fellowship-dev/claude-buddy/.env | cut -d= -f2)" \
-H "Content-Type: application/json" \
-d "$(python3 -c "import json; print(json.dumps({'agent':'${TEAM}.intern','task':'Check deployment for $REPO#$PR — $PR_TITLE','repo':'$REPO','context':'SHA: $PR_SHA. Health URL: $HEALTH_URL. Timeout: ${TIMEOUT_MINUTES:-5} minutes. Checker script: ${CHECKER_SCRIPT}. Run /deployment-checker $PR $REPO.'}))")" \
"http://127.0.0.1:3000/dispatch"
else
echo "[post-merge] Skipping deploy-check dispatch — auto-pull deploys are checked by auto-deploy.sh"
fi
Step 5:在 PR 上留言
gh pr comment $PR --repo $REPO --body "$(cat <<'EOF'
**Post-merge pipeline started** 🚀
| Field | Value |
|-------|-------|
| Deploy method | \`$DEPLOY_METHOD\` |
| Health endpoint | \`$HEALTH_URL\` |
| Timeout | ${TIMEOUT_MINUTES:-5} min |
| Expected SHA | \`${PR_SHA:-unknown}\` |
Deployment checker dispatched. Will apply \`deployed\` or \`deploy-failed\` label when complete.
EOF
)"
Step 6:写报告
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
REPORT="$PYLOT_DIR/reports/$(date +%Y-%m-%d)-post-merge-$(echo $REPO | tr '/' '-')-pr${PR}.md"
cat > "$REPORT" <<EOF
# Post-Merge: $REPO PR #$PR — $PR_TITLE
**Date**: $(date +%Y-%m-%d)
**PR**: [$REPO#$PR]($PR_URL)
**SHA**: $PR_SHA
**Deploy method**: $DEPLOY_METHOD
**Deploy-checker job**: dispatched
## Changed Files
$CHANGED_FILES
## Outcome
Deploy-checker dispatched. Awaiting health confirmation.
EOF
团队配置参考
每个团队的 CLAUDE.md 应包含 deploy: 段。通用示例:
Pylot(auto-pull):
deploy:
method: auto-pull
health_url: "http://localhost:1337/_health"
timeout_minutes: 5
checker_script: "scripts/deployment-checker-pylot.sh"
expected_sha_field: "sha"
Lexgo(GitHub Actions → Fly.io):
deploy:
method: github-actions
health_url: "https://api.lexgo.cl/_health"
timeout_minutes: 30
checker_script: "scripts/deployment-checker-lexgo.sh"
Booster-pack(branch-push → Fly + Vercel):
deploy:
method: auto-pull
health_url: "https://api.booster-pack.dev/health"
timeout_minutes: 10
注意事项
- 没有团队配置 = 不执行任何操作。不要猜测部署方式——缺少配置是应当去配置的信号。
- SHA 比较是尽力而为。如果健康端点未暴露 sha 字段,则回退到时间戳比较。
- post-merge 按 PR 触发。每个合并的 PR 都有自己的 deploy-checker 任务。
- Monorepo:如果仓库有多个部署目标(例如后端 + 前端),每个目标派发一个 checker。checker_script 决定轮询哪个目标。