deployment-checker

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

deployment-checker

部署检查器

Stage 2 of the autonomous deployment pipeline. Polls the team's health endpoint using a cheap bash script until the deployed sha matches the merged PR's sha, or times out. Exits with a structured result and applies
deployed
or
deploy-failed
label.
Design goal: zero token burn during polling. The Claude agent runs only to set up the poll and process the result. All polling is done by the bash script.
自动化部署流水线的第2阶段。使用轻量bash脚本轮询团队的健康检查端点,直到已部署的SHA与合并PR的SHA匹配,或者超时。以结构化结果退出,并应用
deployed
deploy-failed
标签。
设计目标: 轮询过程中不消耗任何令牌。Claude Agent仅负责设置轮询和处理结果,所有轮询工作由bash脚本完成。

When to Use

使用场景

  • Dispatched by
    /post-merge
    after a PR merges
  • Manual:
    /deployment-checker PR_NUMBER org/repo
  • 合并PR后由
    /post-merge
    触发
  • 手动调用:
    /deployment-checker PR_NUMBER org/repo

Invocation

调用方式

/deployment-checker 42 fellowship-dev/pylot
/deployment-checker 42 fellowship-dev/pylot

Arguments

参数

bash
PR_NUMBER=$1    # PR number
REPO=$2         # org/repo

bash
PR_NUMBER=$1    # PR编号
REPO=$2         # 组织/仓库

Runbook

运行手册

Step 0: Dedup Gate

步骤0:重复检查闸门

bash
PR=$1
REPO=$2

ALREADY_DONE=$(gh pr view $PR --repo $REPO --json labels \
  --jq '[.labels[].name] | (contains(["deployed"]) or contains(["deploy-failed"]))')
if [ "$ALREADY_DONE" = "true" ]; then
  echo "[deployment-checker] outcome=\"already complete — deployed or deploy-failed label present\" status=success"
  exit 0
fi
bash
PR=$1
REPO=$2

ALREADY_DONE=$(gh pr view $PR --repo $REPO --json labels \
  --jq '[.labels[].name] | (contains(["deployed"]) or contains(["deploy-failed"]))')
if [ "$ALREADY_DONE" = "true" ]; then
  echo "[deployment-checker] outcome=\"already complete — deployed or deploy-failed label present\" status=success"
  exit 0
fi

Step 1: Read Context from Job

步骤1:从任务中读取上下文

Extract parameters from the job context (set by
/post-merge
):
bash
undefined
从任务上下文中提取参数(由
/post-merge
设置):
bash
undefined

These are passed via job context or derived from PR

这些参数通过任务上下文传递或从PR中获取

PR_URL=$(gh pr view $PR --repo $REPO --json url --jq '.url') PR_TITLE=$(gh pr view $PR --repo $REPO --json title --jq '.title')
PR_URL=$(gh pr view $PR --repo $REPO --json url --jq '.url') PR_TITLE=$(gh pr view $PR --repo $REPO --json title --jq '.title')

From job context (set by post-merge):

来自任务上下文(由post-merge设置):

EXPECTED_SHA — merge commit sha to wait for

EXPECTED_SHA — 等待的合并提交SHA

HEALTH_URL — endpoint to poll

HEALTH_URL — 要轮询的端点

TIMEOUT_MINUTES — max wait (default: 5)

TIMEOUT_MINUTES — 最长等待时间(默认:5分钟)

CHECKER_SCRIPT — path to team bash polling script

CHECKER_SCRIPT — 团队bash轮询脚本的路径

POLL_INTERVAL — seconds between polls (default: 30)

POLL_INTERVAL — 轮询间隔(默认:30秒)

EXPECTED_SHA="${EXPECTED_SHA:-}" HEALTH_URL="${HEALTH_URL:-}" TIMEOUT_MINUTES="${TIMEOUT_MINUTES:-5}" CHECKER_SCRIPT="${CHECKER_SCRIPT:-}" POLL_INTERVAL="${POLL_INTERVAL:-30}"
if [ -z "$HEALTH_URL" ] || [ -z "$CHECKER_SCRIPT" ]; then echo "[deployment-checker] ERROR: HEALTH_URL and CHECKER_SCRIPT must be set in job context" >&2 exit 1 fi
undefined
EXPECTED_SHA="${EXPECTED_SHA:-}" HEALTH_URL="${HEALTH_URL:-}" TIMEOUT_MINUTES="${TIMEOUT_MINUTES:-5}" CHECKER_SCRIPT="${CHECKER_SCRIPT:-}" POLL_INTERVAL="${POLL_INTERVAL:-30}"
if [ -z "$HEALTH_URL" ] || [ -z "$CHECKER_SCRIPT" ]; then echo "[deployment-checker] ERROR: HEALTH_URL and CHECKER_SCRIPT must be set in job context" >&2 exit 1 fi
undefined

Step 2: Run the Bash Polling Script

步骤2:运行Bash轮询脚本

The team-specific bash script handles the actual polling. This keeps Claude out of the loop during the wait.
bash
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
团队专属的bash脚本处理实际轮询工作。这让Claude在等待过程中无需参与。
bash
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"

Resolve checker script path

解析检查器脚本路径

if [[ "$CHECKER_SCRIPT" != /* ]]; then CHECKER_SCRIPT="$PYLOT_DIR/$CHECKER_SCRIPT" fi
if [ ! -f "$CHECKER_SCRIPT" ]; then echo "[deployment-checker] ERROR: checker script not found: $CHECKER_SCRIPT" >&2 DEPLOY_STATUS="failed" FAILURE_REASON="checker script not found: $CHECKER_SCRIPT" else chmod +x "$CHECKER_SCRIPT"

Run the script; it exits 0 on success, non-zero on failure/timeout

stdout: "deploy_status=success sha=abc123" or "deploy_status=failed reason=OOM"

POLL_OUTPUT=$(HEALTH_URL="$HEALTH_URL"
EXPECTED_SHA="$EXPECTED_SHA"
TIMEOUT_MINUTES="$TIMEOUT_MINUTES"
POLL_INTERVAL="$POLL_INTERVAL"
bash "$CHECKER_SCRIPT" 2>&1) && POLL_EXIT=0 || POLL_EXIT=$?
echo "Checker output: $POLL_OUTPUT"

Parse structured output (python3 avoids grep -P which is unavailable on macOS/BSD)

DEPLOY_STATUS=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'deploy_status=(\S+)', sys.stdin.read()); print(m.group(1) if m else 'failed')" 2>/dev/null || echo "failed") DEPLOYED_SHA=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'sha=(\S+)', sys.stdin.read()); print(m.group(1) if m else '')" 2>/dev/null || echo "") FAILURE_REASON=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'reason=(.+)', sys.stdin.read()); print(m.group(1).strip() if m else 'unknown')" 2>/dev/null || echo "unknown") fi
undefined
if [[ "$CHECKER_SCRIPT" != /* ]]; then CHECKER_SCRIPT="$PYLOT_DIR/$CHECKER_SCRIPT" fi
if [ ! -f "$CHECKER_SCRIPT" ]; then echo "[deployment-checker] ERROR: checker script not found: $CHECKER_SCRIPT" >&2 DEPLOY_STATUS="failed" FAILURE_REASON="checker script not found: $CHECKER_SCRIPT" else chmod +x "$CHECKER_SCRIPT"

运行脚本;成功时退出码为0,失败/超时为非0

标准输出:"deploy_status=success sha=abc123" 或 "deploy_status=failed reason=OOM"

POLL_OUTPUT=$(HEALTH_URL="$HEALTH_URL"
EXPECTED_SHA="$EXPECTED_SHA"
TIMEOUT_MINUTES="$TIMEOUT_MINUTES"
POLL_INTERVAL="$POLL_INTERVAL"
bash "$CHECKER_SCRIPT" 2>&1) && POLL_EXIT=0 || POLL_EXIT=$?
echo "Checker output: $POLL_OUTPUT"

解析结构化输出(使用python3避免依赖grep -P,该命令在macOS/BSD中不可用)

DEPLOY_STATUS=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'deploy_status=(\S+)', sys.stdin.read()); print(m.group(1) if m else 'failed')" 2>/dev/null || echo "failed") DEPLOYED_SHA=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'sha=(\S+)', sys.stdin.read()); print(m.group(1) if m else '')" 2>/dev/null || echo "") FAILURE_REASON=$(echo "$POLL_OUTPUT" | python3 -c "import sys,re; m=re.search(r'reason=(.+)', sys.stdin.read()); print(m.group(1).strip() if m else 'unknown')" 2>/dev/null || echo "unknown") fi
undefined

Step 3: Apply Label and Comment

步骤3:应用标签并添加评论

On success:
bash
if [ "$DEPLOY_STATUS" = "success" ]; then
  # Create label if missing
  gh label create "deployed" --repo $REPO --color "0e8a16" \
    --description "Deploy verified — health check passed" 2>/dev/null || true

  gh pr edit $PR --repo $REPO --add-label "deployed"

  gh pr comment $PR --repo $REPO --body "$(cat <<EOF
**Deployment verified** ✅

| Field | Value |
|-------|-------|
| Status | \`success\` |
| Deployed SHA | \`${DEPLOYED_SHA:-confirmed}\` |
| Health URL | \`$HEALTH_URL\` |
| Waited | up to ${TIMEOUT_MINUTES} min |

The \`deployed\` label triggers post-deploy file-match actions.
EOF
)"
fi
On failure or timeout:
bash
if [ "$DEPLOY_STATUS" != "success" ]; then
  # Create label if missing
  gh label create "deploy-failed" --repo $REPO --color "d93f0b" \
    --description "Deploy verification failed or timed out" 2>/dev/null || true

  gh pr edit $PR --repo $REPO --add-label "deploy-failed"

  gh pr comment $PR --repo $REPO --body "$(cat <<EOF
**Deployment check failed** ❌

| Field | Value |
|-------|-------|
| Status | \`failed\` |
| Reason | ${FAILURE_REASON} |
| Health URL | \`$HEALTH_URL\` |
| Timeout | ${TIMEOUT_MINUTES} min |
| Expected SHA | \`${EXPECTED_SHA:-unknown}\` |

**Action required:** Check the deployment manually. Remove \`deploy-failed\` and re-add \`deployed\` once confirmed.
EOF
)"
fi
成功时:
bash
if [ "$DEPLOY_STATUS" = "success" ]; then
  # 若标签不存在则创建
  gh label create "deployed" --repo $REPO --color "0e8a16" \
    --description "Deploy verified — health check passed" 2>/dev/null || true

  gh pr edit $PR --repo $REPO --add-label "deployed"

  gh pr comment $PR --repo $REPO --body "$(cat <<EOF
**部署已验证** ✅

| 字段 | 值 |
|-------|-------|
| 状态 | \`success\` |
| 已部署SHA | \`${DEPLOYED_SHA:-confirmed}\` |
| 健康检查URL | \`$HEALTH_URL\` |
| 等待时长 | 最多${TIMEOUT_MINUTES}分钟 |

\`deployed\`标签会触发部署后的文件匹配操作。
EOF
)"
fi
失败或超时:
bash
if [ "$DEPLOY_STATUS" != "success" ]; then
  # 若标签不存在则创建
  gh label create "deploy-failed" --repo $REPO --color "d93f0b" \
    --description "Deploy verification failed or timed out" 2>/dev/null || true

  gh pr edit $PR --repo $REPO --add-label "deploy-failed"

  gh pr comment $PR --repo $REPO --body "$(cat <<EOF
**部署检查失败** ❌

| 字段 | 值 |
|-------|-------|
| 状态 | \`failed\` |
| 原因 | ${FAILURE_REASON} |
| 健康检查URL | \`$HEALTH_URL\` |
| 超时时间 | ${TIMEOUT_MINUTES}分钟 |
| 预期SHA | \`${EXPECTED_SHA:-unknown}\` |

**需手动操作:** 请手动检查部署状态。确认无误后,移除\`deploy-failed\`标签并重新添加\`deployed\`标签。
EOF
)"
fi

Step 4: Write Report

步骤4:生成报告

bash
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
REPORT="$PYLOT_DIR/reports/$(date +%Y-%m-%d)-deployment-checker-$(echo $REPO | tr '/' '-')-pr${PR}.md"

cat > "$REPORT" <<EOF
bash
PYLOT_DIR="${PYLOT_DIR:-$HOME/projects/fellowship-dev/pylot}"
REPORT="$PYLOT_DIR/reports/$(date +%Y-%m-%d)-deployment-checker-$(echo $REPO | tr '/' '-')-pr${PR}.md"

cat > "$REPORT" <<EOF

Deployment Checker: $REPO PR #$PR

部署检查器:$REPO PR #$PR

Date: $(date +%Y-%m-%d) PR: $REPO#$PR — $PR_TITLE Status: $DEPLOY_STATUS SHA: ${DEPLOYED_SHA:-not confirmed} Reason: ${FAILURE_REASON:-n/a}
日期: $(date +%Y-%m-%d) PR: $REPO#$PR — $PR_TITLE 状态: $DEPLOY_STATUS SHA: ${DEPLOYED_SHA:-未确认} 原因: ${FAILURE_REASON:-无}

Poll Output

轮询输出

``` $POLL_OUTPUT ``` EOF

---
``` $POLL_OUTPUT ``` EOF

---

Team Checker Script Interface

团队检查器脚本接口

Each team provides a bash script at a path configured in their CLAUDE.md. The script must:
Inputs (env vars):
VariableRequiredDescription
HEALTH_URL
yesURL to GET for health check
EXPECTED_SHA
noGit sha to wait for (compare against health response)
TIMEOUT_MINUTES
noMax wait in minutes (default: 5)
POLL_INTERVAL
noSeconds between polls (default: 30)
Exit codes:
  • 0
    — deployment confirmed
  • 1
    — timeout or failure
stdout format (last line):
deploy_status=success sha=abc123def456
deploy_status=failed reason=timeout_after_5min
deploy_status=failed reason=health_check_returned_503
Example minimal script:
bash
#!/bin/bash
每个团队在其CLAUDE.md配置的路径下提供一个bash脚本。该脚本必须满足以下要求:
输入(环境变量):
变量是否必填说明
HEALTH_URL
用于健康检查的GET请求URL
EXPECTED_SHA
等待的Git SHA(与健康响应中的值对比)
TIMEOUT_MINUTES
最长等待时间(分钟,默认:5)
POLL_INTERVAL
轮询间隔(秒,默认:30)
退出码:
  • 0
    — 部署已确认
  • 1
    — 超时或失败
标准输出格式(最后一行):
deploy_status=success sha=abc123def456
deploy_status=failed reason=timeout_after_5min
deploy_status=failed reason=health_check_returned_503
示例极简脚本:
bash
#!/bin/bash

scripts/deployment-checker-pylot.sh

scripts/deployment-checker-pylot.sh

TIMEOUT=$((${TIMEOUT_MINUTES:-5} * 60)) INTERVAL=${POLL_INTERVAL:-30} ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do RESPONSE=$(curl -sf "$HEALTH_URL" 2>/dev/null || echo "") CURRENT_SHA=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('sha',''))" 2>/dev/null || echo "")
if [ -n "$EXPECTED_SHA" ] && [ "$CURRENT_SHA" = "$EXPECTED_SHA" ]; then echo "deploy_status=success sha=$CURRENT_SHA" exit 0 elif [ -z "$EXPECTED_SHA" ] && [ -n "$RESPONSE" ]; then # No sha to compare — just verify health returns 200 echo "deploy_status=success sha=confirmed" exit 0 fi
sleep $INTERVAL ELAPSED=$((ELAPSED + INTERVAL)) done
echo "deploy_status=failed reason=timeout_after_${TIMEOUT_MINUTES:-5}min" exit 1

---
TIMEOUT=$((${TIMEOUT_MINUTES:-5} * 60)) INTERVAL=${POLL_INTERVAL:-30} ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do RESPONSE=$(curl -sf "$HEALTH_URL" 2>/dev/null || echo "") CURRENT_SHA=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('sha',''))" 2>/dev/null || echo "")
if [ -n "$EXPECTED_SHA" ] && [ "$CURRENT_SHA" = "$EXPECTED_SHA" ]; then echo "deploy_status=success sha=$CURRENT_SHA" exit 0 elif [ -z "$EXPECTED_SHA" ] && [ -n "$RESPONSE" ]; then # 无需对比SHA — 仅验证健康检查返回200 echo "deploy_status=success sha=confirmed" exit 0 fi
sleep $INTERVAL ELAPSED=$((ELAPSED + INTERVAL)) done
echo "deploy_status=failed reason=timeout_after_${TIMEOUT_MINUTES:-5}min" exit 1

---

Notes

注意事项

  • Zero token burn during polling. Claude only runs before and after the bash script. The bash script does all the waiting.
  • SHA comparison is optional. If
    EXPECTED_SHA
    is empty, the checker confirms health endpoint returns 200.
  • Timeout is team-configurable. 5 min for Pylot (auto-pull), 30 min for Lexgo (GitHub Actions + Fly.io).
  • deployed
    label chains into
    /post-deploy
    via event-rules — no manual handoff needed.
  • Monorepo targets: dispatch one deployment-checker job per target if needed.
  • 轮询过程零令牌消耗:Claude仅在bash脚本运行前后执行,所有等待工作由bash脚本完成。
  • SHA对比可选:如果
    EXPECTED_SHA
    为空,检查器仅确认健康端点返回200状态码。
  • 超时时间可由团队配置:Pylot(自动拉取)为5分钟,Lexgo(GitHub Actions + Fly.io)为30分钟。
  • deployed
    标签通过事件规则触发
    /post-deploy
    :无需手动交接。
  • 单仓多目标:如有需要,可为每个目标触发一个部署检查器任务。