post-merge
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepost-merge
post-merge(合并后部署)
Stage 1 of the autonomous deployment pipeline. Runs after a PR merges: reads the diff, determines whether the deploy is automatic or manual, optionally performs the manual deploy, then dispatches a job to poll until live.
deployment-checker这是自动化部署流水线的第1阶段。在PR合并后运行:读取代码差异,判断部署类型为自动或手动,可选择执行手动部署,然后调度任务进行轮询直至服务上线。
deployment-checkerWhen to Use
使用场景
- Triggered automatically by event-rules when a PR merges to the default branch
- Manual:
/post-merge PR_NUMBER org/repo
- 当PR合并到默认分支时,由event-rules自动触发
- 手动触发:
/post-merge PR_NUMBER org/repo
Invocation
调用方式
/post-merge 42 fellowship-dev/pylot/post-merge 42 fellowship-dev/pylotArguments
参数说明
bash
PR_NUMBER=$1 # PR number
REPO=$2 # org/repobash
PR_NUMBER=$1 # PR number
REPO=$2 # org/repoRunbook
执行手册
Step 0: Dedup Gate
步骤0:重复请求拦截
bash
PR=$1
REPO=$2bash
PR=$1
REPO=$2Check if deployment-checker job already dispatched for this PR
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
${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
undefinedEXISTING=$(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
${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
undefinedStep 1: Gather PR Context
步骤1:收集PR上下文信息
bash
undefinedbash
undefinedFetch PR metadata
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"
undefinedPR_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"
undefinedStep 2: Read Team Deploy Configuration
步骤2:读取团队部署配置
Read the team's CLAUDE.md to determine the deploy method configured for this repo. Look for a section:
deploy:yaml
undefined读取团队的CLAUDE.md文件,确定此仓库配置的部署方式。查找章节:
deploy:yaml
undefinedExample in crew/infra/CLAUDE.md or team CLAUDE.md:
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
**Deploy method decision tree:**
| Method | Action |
|--------|--------|
| `auto-pull` | Deploy is automatic. Skip Step 3 and Step 4 (auto-deploy.sh handles deploy-check). |
| `github-actions` | Deploy triggered by merge. Wait ~2 min, then Step 4. |
| `manual` | CTO runs deploy command now (Step 3), then Step 4. |
If no deploy config found, emit a warning and exit — do not guess.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
**部署方式决策树:**
| 方式 | 操作 |
|--------|--------|
| `auto-pull` | 自动部署。跳过步骤3和步骤4(auto-deploy.sh会处理部署检查)。 |
| `github-actions` | 合并时触发部署。等待约2分钟后执行步骤4。 |
| `manual` | 由CTO执行部署命令(步骤3),然后执行步骤4。 |
如果未找到部署配置,发出警告并退出——请勿猜测。Step 3: Manual Deploy (if method = manual)
步骤3:手动部署(当方式为manual时)
Only execute when . Read the deploy command from team config:
method: manualbash
undefined仅在时执行。从团队配置中读取部署命令:
method: manualbash
undefinedExample: manual deploy for a Fly.io app
Example: manual deploy for a Fly.io app
cd /path/to/repo && fly deploy --remote-only
cd /path/to/repo && fly deploy --remote-only
Example: manual ECR push
Example: manual ECR push
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
docker build -t $IMAGE . && docker push $IMAGE
Document the command executed and its output.aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
docker build -t $IMAGE . && docker push $IMAGE
记录执行的命令及其输出。Step 4: Dispatch deployment-checker Job
步骤4:调度deployment-checker任务
Skip this step if . Auto-pull repos have their deploy-check dispatched by after the executor restarts — dispatching here would create a chicken-and-egg deadlock (deploy-checker blocks the queue, which blocks the deploy it's waiting for).
method: auto-pullauto-deploy.shFor other deploy methods (, ), dispatch a new job:
github-actionsmanualbash
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当时跳过此步骤。 使用auto-pull的仓库会在executor重启后由调度部署检查——在此处调度会造成循环依赖死锁(deployment-checker阻塞队列,进而阻塞它等待的部署)。
method: auto-pullauto-deploy.sh对于其他部署方式(、),调度新任务:
github-actionsmanualbash
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"
fiStep 5: Post Comment on PR
步骤5:在PR上发布评论
bash
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
)"bash
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: Write Report
步骤6:生成报告
bash
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" <<EOFbash
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" <<EOFPost-Merge: $REPO PR #$PR — $PR_TITLE
Post-Merge: $REPO PR #$PR — $PR_TITLE
Date: $(date +%Y-%m-%d)
PR: $REPO#$PR
SHA: $PR_SHA
Deploy method: $DEPLOY_METHOD
Deploy-checker job: dispatched
Date: $(date +%Y-%m-%d)
PR: $REPO#$PR
SHA: $PR_SHA
Deploy method: $DEPLOY_METHOD
Deploy-checker job: dispatched
Changed Files
Changed Files
$CHANGED_FILES
$CHANGED_FILES
Outcome
Outcome
Deploy-checker dispatched. Awaiting health confirmation.
EOF
---Deploy-checker dispatched. Awaiting health confirmation.
EOF
---Team Configuration Reference
团队配置参考
Each team's CLAUDE.md should contain a section. Generic examples:
deploy:每个团队的CLAUDE.md文件应包含章节。通用示例:
deploy:Pylot (auto-pull)
Pylot(auto-pull方式)
yaml
deploy:
method: auto-pull
health_url: "http://localhost:1337/_health"
timeout_minutes: 5
checker_script: "scripts/deployment-checker-pylot.sh"
expected_sha_field: "sha"yaml
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)
Lexgo(GitHub Actions → Fly.io)
yaml
deploy:
method: github-actions
health_url: "https://api.lexgo.cl/_health"
timeout_minutes: 30
checker_script: "scripts/deployment-checker-lexgo.sh"yaml
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)
Booster-pack(分支推送 → Fly + Vercel)
yaml
deploy:
method: auto-pull
health_url: "https://api.booster-pack.dev/health"
timeout_minutes: 10yaml
deploy:
method: auto-pull
health_url: "https://api.booster-pack.dev/health"
timeout_minutes: 10Notes
注意事项
- No team config = no action. Don't guess deploy methods — missing config is a signal to configure.
- SHA comparison is best-effort. If health endpoint doesn't expose a sha field, fall back to timestamp comparison.
- Post-merge fires per-PR. Each merged PR gets its own deploy-checker job.
- Monorepo: if the repo has multiple deploy targets (e.g., backend + frontend), dispatch one checker per target. The checker_script determines which target to poll.
- 无团队配置则不执行任何操作。 请勿猜测部署方式——缺失配置意味着需要先进行配置。
- SHA对比为尽力而为。 如果健康检查端点未暴露sha字段,则回退到时间戳对比。
- 合并后流程按PR触发。 每个合并的PR都会获得独立的deployment-checker任务。
- 单体仓库: 如果仓库有多个部署目标(例如后端+前端),则为每个目标调度一个检查任务。checker_script会决定轮询哪个目标。