post-deploy
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepost-deploy
部署后操作
Stage 3 of the autonomous deployment pipeline. Triggered by the label. Reads the PR's changed files, matches them against configured patterns, and runs targeted actions: ECR image rebuilds, config reload verification, smoke tests, or custom scripts.
deployed自主部署流水线的第3阶段。由标签触发。读取PR的变更文件,将其与配置的模式进行匹配,并运行针对性操作:ECR镜像重建、配置重载验证、冒烟测试或自定义脚本。
deployedWhen to Use
使用场景
- Triggered by label via event-rules (after deployment-checker confirms live deploy)
deployed - Manual:
/post-deploy PR_NUMBER org/repo
- 通过事件规则由标签触发(在deployment-checker确认部署上线后)
deployed - 手动触发:
/post-deploy PR_NUMBER org/repo
Invocation
调用方式
/post-deploy 42 fellowship-dev/pylot/post-deploy 42 fellowship-dev/pylotRunbook
运行手册
Step 0: Dedup Gate
步骤0:重复执行检查 gate
bash
PR=$1
REPO=$2bash
PR=$1
REPO=$2Check if post-deploy already ran (look for a post-deploy comment)
Check if post-deploy already ran (look for a post-deploy comment)
EXISTING=$(gh pr view $PR --repo $REPO --json comments
--jq '.comments[].body' | grep "Post-deploy actions" || true) if [ -n "$EXISTING" ]; then echo "[post-deploy] outcome="already complete — post-deploy comment found" status=success" exit 0 fi
--jq '.comments[].body' | grep "Post-deploy actions" || true) if [ -n "$EXISTING" ]; then echo "[post-deploy] outcome="already complete — post-deploy comment found" status=success" exit 0 fi
undefinedEXISTING=$(gh pr view $PR --repo $REPO --json comments
--jq '.comments[].body' | grep "Post-deploy actions" || true) if [ -n "$EXISTING" ]; then echo "[post-deploy] outcome="already complete — post-deploy comment found" status=success" exit 0 fi
--jq '.comments[].body' | grep "Post-deploy actions" || true) if [ -n "$EXISTING" ]; then echo "[post-deploy] outcome="already complete — post-deploy comment found" status=success" exit 0 fi
undefinedStep 1: Read PR Context
步骤1:读取PR上下文
bash
PR_DATA=$(gh pr view $PR --repo $REPO --json number,title,url,files,mergeCommit,baseRefName)
PR_TITLE=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
PR_URL=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])")
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']))
")
echo "PR: $PR — $PR_TITLE"
echo "Changed files:"
echo "$CHANGED_FILES"bash
PR_DATA=$(gh pr view $PR --repo $REPO --json number,title,url,files,mergeCommit,baseRefName)
PR_TITLE=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
PR_URL=$(echo "$PR_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])")
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']))
")
echo "PR: $PR — $PR_TITLE"
echo "Changed files:"
echo "$CHANGED_FILES"Step 2: Read Team Post-Deploy Configuration
步骤2:读取团队部署后配置
Read the team's CLAUDE.md for rules. Each rule maps a file glob pattern to an action:
post_deploy:yaml
undefined读取团队的CLAUDE.md文件中的规则。每条规则将一个文件通配符模式映射到一个操作:
post_deploy:yaml
undefinedExample in team CLAUDE.md:
Example in team CLAUDE.md:
post_deploy:
- match: "Dockerfile*" action: ecr-rebuild description: "Rebuild and push ECR image"
- match: "docker-entrypoint.sh" action: ecr-rebuild
- match: "crew.yml" action: config-reload-verify description: "Verify gateway reloaded updated config"
- match: "scripts/*.sh" action: executor-verify description: "Spot-check executor behavior"
- match: "event-rules.yml" action: config-reload-verify
- match: "**" action: smoke-test optional: true description: "Optional smoke test for any PR"
**Action types:**
| Action | Description |
|--------|-------------|
| `ecr-rebuild` | Rebuild Docker image and push to ECR |
| `config-reload-verify` | Verify the process/gateway has reloaded the updated config |
| `executor-verify` | Spot-check executor or script behavior |
| `smoke-test` | Run team-defined smoke test |
| `custom` | Run `command` field as shell command |post_deploy:
- match: "Dockerfile*" action: ecr-rebuild description: "Rebuild and push ECR image"
- match: "docker-entrypoint.sh" action: ecr-rebuild
- match: "crew.yml" action: config-reload-verify description: "Verify gateway reloaded updated config"
- match: "scripts/*.sh" action: executor-verify description: "Spot-check executor behavior"
- match: "event-rules.yml" action: config-reload-verify
- match: "**" action: smoke-test optional: true description: "Optional smoke test for any PR"
**操作类型:**
| 操作 | 描述 |
|--------|-------------|
| `ecr-rebuild` | 重建Docker镜像并推送到ECR |
| `config-reload-verify` | 验证进程/网关已重载更新后的配置 |
| `executor-verify` | 抽查执行器或脚本行为 |
| `smoke-test` | 运行团队定义的冒烟测试 |
| `custom` | 将`command`字段作为Shell命令运行 |Step 3: Match Files to Actions
步骤3:匹配文件与操作
python
undefinedpython
undefinedPseudocode — implement in bash with Python for glob matching
Pseudocode — implement in bash with Python for glob matching
import fnmatch
changed = CHANGED_FILES.splitlines()
triggered_actions = []
for rule in post_deploy_rules:
pattern = rule['match']
matching = [f for f in changed if fnmatch.fnmatch(f, pattern)]
if matching:
triggered_actions.append({
'action': rule['action'],
'description': rule.get('description', rule['action']),
'matched_files': matching,
'optional': rule.get('optional', False)
})
undefinedimport fnmatch
changed = CHANGED_FILES.splitlines()
triggered_actions = []
for rule in post_deploy_rules:
pattern = rule['match']
matching = [f for f in changed if fnmatch.fnmatch(f, pattern)]
if matching:
triggered_actions.append({
'action': rule['action'],
'description': rule.get('description', rule['action']),
'matched_files': matching,
'optional': rule.get('optional', False)
})
undefinedStep 4: Execute Actions
步骤4:执行操作
Run each triggered action. Document result for each.
运行每个触发的操作,并记录每个操作的结果。
Action: ecr-rebuild
操作:ecr-rebuild
bash
undefinedbash
undefinedRebuild and push ECR image
Rebuild and push ECR image
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
ECR_URI="${ECR_URI:-$(grep 'fargate_ecr_uri' $PYLOT_DIR/crew.yml | awk '{print $2}')}"
cd "$PYLOT_DIR"
bash scripts/build-worker-image.sh 2>&1
ECR_EXIT=$?
[ $ECR_EXIT -eq 0 ] && echo "ecr-rebuild: success" || echo "ecr-rebuild: failed exit=$ECR_EXIT"
undefinedPYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
ECR_URI="${ECR_URI:-$(grep 'fargate_ecr_uri' $PYLOT_DIR/crew.yml | awk '{print $2}')}"
cd "$PYLOT_DIR"
bash scripts/build-worker-image.sh 2>&1
ECR_EXIT=$?
[ $ECR_EXIT -eq 0 ] && echo "ecr-rebuild: success" || echo "ecr-rebuild: failed exit=$ECR_EXIT"
undefinedAction: config-reload-verify
操作:config-reload-verify
bash
undefinedbash
undefinedVerify the executor/gateway has reloaded the updated config
Verify the executor/gateway has reloaded the updated config
For Pylot: check that crew.yml timestamp matches what executor loaded
For Pylot: check that crew.yml timestamp matches what executor loaded
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
Check if executor is running and has loaded recent config
Check if executor is running and has loaded recent config
CREW_MTIME=$(stat -c %Y "$PYLOT_DIR/crew.yml" 2>/dev/null || stat -f %m "$PYLOT_DIR/crew.yml" 2>/dev/null)
EXECUTOR_PID=$(pgrep -f "executor.sh" || true)
if [ -n "$EXECUTOR_PID" ]; then
echo "config-reload-verify: executor running (pid=$EXECUTOR_PID). crew.yml mtime=$CREW_MTIME."
echo "config-reload-verify: success — executor will pick up config on next poll cycle"
else
echo "config-reload-verify: WARNING — executor not running. Config will load on restart."
fi
undefinedCREW_MTIME=$(stat -c %Y "$PYLOT_DIR/crew.yml" 2>/dev/null || stat -f %m "$PYLOT_DIR/crew.yml" 2>/dev/null)
EXECUTOR_PID=$(pgrep -f "executor.sh" || true)
if [ -n "$EXECUTOR_PID" ]; then
echo "config-reload-verify: executor running (pid=$EXECUTOR_PID). crew.yml mtime=$CREW_MTIME."
echo "config-reload-verify: success — executor will pick up config on next poll cycle"
else
echo "config-reload-verify: WARNING — executor not running. Config will load on restart."
fi
undefinedAction: executor-verify
操作:executor-verify
bash
undefinedbash
undefinedSpot-check executor behavior — run a quick test
Spot-check executor behavior — run a quick test
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
Syntax-check the modified scripts
Syntax-check the modified scripts
for script in $MATCHED_SCRIPTS; do
bash -n "$PYLOT_DIR/$script" && echo "executor-verify: $script syntax OK" || echo "executor-verify: $script SYNTAX ERROR"
done
undefinedfor script in $MATCHED_SCRIPTS; do
bash -n "$PYLOT_DIR/$script" && echo "executor-verify: $script syntax OK" || echo "executor-verify: $script SYNTAX ERROR"
done
undefinedAction: smoke-test
操作:smoke-test
Run the team's configured smoke test. Optional by default.
bash
undefined运行团队配置的冒烟测试。默认情况下为可选操作。
bash
undefinedTeam-specific smoke test — read from CLAUDE.md smoke_test.command
Team-specific smoke test — read from CLAUDE.md smoke_test.command
Example for Pylot: run a dry-run event dispatch
Example for Pylot: run a dry-run event dispatch
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
bash "$PYLOT_DIR/event-router.sh" --dry-run 2>&1 | head -20
echo "smoke-test: event-router dry-run complete"
undefinedPYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
bash "$PYLOT_DIR/event-router.sh" --dry-run 2>&1 | head -20
echo "smoke-test: event-router dry-run complete"
undefinedStep 5: Post Summary Comment
步骤5:发布总结评论
bash
undefinedbash
undefinedBuild table rows before heredoc (<<'EOF' prevents expansion — pre-build instead)
Build table rows before heredoc (<<'EOF' prevents expansion — pre-build instead)
ACTION_TABLE=""
for action_entry in $TRIGGERED_ACTION_ENTRIES; do
ACTION_NAME=$(echo "$action_entry" | cut -d: -f1)
ACTION_FILES=$(echo "$action_entry" | cut -d: -f2)
ACTION_RESULT=$(echo "$action_entry" | cut -d: -f3)
ACTION_TABLE="${ACTION_TABLE}| ${ACTION_NAME} | ${ACTION_FILES} | ${ACTION_RESULT} |\n"
done
[ -z "$ACTION_TABLE" ] && ACTION_TABLE="No file-match rules triggered for this PR's changed files.\n"
gh pr comment $PR --repo $REPO --body "$(printf '%s' "## Post-deploy actions
| Action | Matched Files | Result |
|---|---|---|
| $(printf '%b' "$ACTION_TABLE") | ||
| Pipeline complete. This PR is live and verified.")" |
undefinedACTION_TABLE=""
for action_entry in $TRIGGERED_ACTION_ENTRIES; do
ACTION_NAME=$(echo "$action_entry" | cut -d: -f1)
ACTION_FILES=$(echo "$action_entry" | cut -d: -f2)
ACTION_RESULT=$(echo "$action_entry" | cut -d: -f3)
ACTION_TABLE="${ACTION_TABLE}| ${ACTION_NAME} | ${ACTION_FILES} | ${ACTION_RESULT} |\n"
done
[ -z "$ACTION_TABLE" ] && ACTION_TABLE="No file-match rules triggered for this PR's changed files.\n"
gh pr comment $PR --repo $REPO --body "$(printf '%s' "## Post-deploy actions
| Action | Matched Files | Result |
|---|---|---|
| $(printf '%b' "$ACTION_TABLE") | ||
| Pipeline complete. This PR is live and verified.")" |
undefinedStep 6: Write Report
步骤6:生成报告
bash
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
REPORT="$PYLOT_DIR/reports/$(date +%Y-%m-%d)-post-deploy-$(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-deploy-$(echo $REPO | tr '/' '-')-pr${PR}.md"
cat > "$REPORT" <<EOFPost-Deploy: $REPO PR #$PR — $PR_TITLE
Post-Deploy: $REPO PR #$PR — $PR_TITLE
Date: $(date +%Y-%m-%d)
PR: $REPO#$PR
Date: $(date +%Y-%m-%d)
PR: $REPO#$PR
Changed Files
Changed Files
$CHANGED_FILES
$CHANGED_FILES
Actions Triggered
Actions Triggered
$TRIGGERED_ACTIONS_SUMMARY
$TRIGGERED_ACTIONS_SUMMARY
Results
Results
$ACTIONS_RESULTS
EOF
---$ACTIONS_RESULTS
EOF
---Default File-Match Rules (Pylot)
默认文件匹配规则(Pylot)
| Pattern | Action | When |
|---|---|---|
| ecr-rebuild | Docker image changed |
| ecr-rebuild | Entrypoint script changed |
| config-reload-verify | Crew config changed |
| config-reload-verify | Event routing changed |
| executor-verify | Shell scripts changed |
| executor-verify | Dispatch logic changed |
| executor-verify | Executor logic changed |
| 模式 | 操作 | 触发场景 |
|---|---|---|
| ecr-rebuild | Docker镜像变更 |
| ecr-rebuild | 入口脚本变更 |
| config-reload-verify | Crew配置变更 |
| config-reload-verify | 事件路由变更 |
| executor-verify | Shell脚本变更 |
| executor-verify | 调度逻辑变更 |
| executor-verify | 执行器逻辑变更 |
Notes
注意事项
- No matched rules = no actions. A PR touching only Markdown files does nothing. This is correct behavior.
- Optional actions (smoke-test) run but do not affect the summary verdict.
- ecr-rebuild failure should be surfaced as a comment but does not re-apply — the deploy itself was verified. File a follow-up issue instead.
deploy-failed - Team config is the authority. Generic skill + team CLAUDE.md = full configurability without modifying the skill.
- Monorepo: match files against per-target rules if the team has multiple deploy targets.
- 无匹配规则则不执行任何操作:仅修改Markdown文件的PR不会触发任何操作,这是正常行为。
- 可选操作(如冒烟测试)会运行,但不会影响总结结论。
- ecr-rebuild失败应在评论中提示,但不会重新应用标签——因为部署本身已验证通过。应提交后续问题跟进。
deploy-failed - 团队配置为权威:通用技能 + 团队CLAUDE.md = 无需修改技能即可实现全配置化。
- 单体仓库:如果团队有多个部署目标,需针对每个目标的规则匹配文件。