我的好朋友 Claude
Claude Code 上線完即核實:自動 ping Vercel preview + smoke test + Slack 通知
第 124 期

Claude Code 上線完即核實:自動 ping Vercel preview + smoke test + Slack 通知

進深·科技
第 124 期|Claude Code|創作者、打工仔|

Push 唔代表部署成功。教 Claude Code 一套上線後嘅例行核實:poll Vercel API 等 build ready、ping 關鍵路徑、跑 Playwright smoke、Slack 報生死。一個 /ship 搞掂。

難度 ★★時間 35 分鐘用具 Claude Code CLI、Vercel / Netlify / Cloudflare Pages account、Slack / Discord webhook
【編者撰】一個香港人

情境

你 indie 做緊個 SaaS,禮拜五夜晚 push 完一個 pricing page 嘅改動,落街食飯。返到屋企打開 laptop —— Slack 三個用戶 ping 你:「點解 /pricing 出 500?」

睇 Vercel dashboard,build 綠色。睇 GitHub,合併咗。但個 page 真係 500。原因:你改咗個 env var name,本地 .env.local 有,Vercel project setting 冇加。Build 過得,runtime 死。

呢類錯,push 成功 ≠ 部署成功 ≠ 用戶見到嘅嘢正常。Vercel 個綠剔淨係話 build pipeline 行得,唔保證個 page render 到。

呢篇拆 4 步,教 Claude Code 喺你打完 git push 之後,自動行一套上線後嘅例行核實:等 deploy ready、ping 關鍵路徑、跑 Playwright smoke、Slack 報生死。出事第一個知嘅係你,唔係你啲用戶。

跟住做

1. Poll Vercel API 等 deploy ready

Push 完即跑 smoke test 會撞「deploy 仲未起好」。要先 poll Vercel API 等 state 變 READY

.claude/scripts/wait-deploy.sh

#!/usr/bin/env bash
set -euo pipefail

PROJECT_ID="${VERCEL_PROJECT_ID:?missing}"
TOKEN="${VERCEL_TOKEN:?missing}"
SHA="$(git rev-parse HEAD)"

for i in {1..40}; do
  RES=$(curl -s "https://api.vercel.com/v6/deployments?projectId=$PROJECT_ID&limit=5" \
    -H "Authorization: Bearer $TOKEN")
  URL=$(echo "$RES" | jq -r ".deployments[] | select(.meta.githubCommitSha==\"$SHA\") | .url" | head -1)
  STATE=$(echo "$RES" | jq -r ".deployments[] | select(.meta.githubCommitSha==\"$SHA\") | .state" | head -1)

  if [ "$STATE" = "READY" ]; then
    echo "https://$URL"
    exit 0
  fi
  if [ "$STATE" = "ERROR" ] || [ "$STATE" = "CANCELED" ]; then
    echo "deploy failed: $STATE" >&2
    exit 1
  fi
  sleep 10
done
echo "timeout waiting for deploy" >&2
exit 1

呢段每 10 秒 poll 一次,最多等 400 秒(一般 build 兩三分鐘)。Token 喺 Vercel account settings 開,權限限定唯讀 deployments 已經夠。最後吐返個 preview URL 畀下游用。

2. Critical path smoke check

有咗 preview URL,第一輪驗證係HTTP 層:關鍵 page 回 200,response body 含預期關鍵字。唔使任何 browser、又快又慳。

.claude/scripts/smoke-paths.sh

#!/usr/bin/env bash
BASE="$1"
PATHS=(
  "/|首頁"
  "/pricing|月費"
  "/api/health|ok"
)

FAIL=0
for entry in "${PATHS[@]}"; do
  IFS='|' read -r path expect <<< "$entry"
  RES=$(curl -s -w "\n%{http_code}" "$BASE$path")
  CODE=$(echo "$RES" | tail -1)
  BODY=$(echo "$RES" | sed '$d')

  if [ "$CODE" != "200" ]; then
    echo "FAIL $path → HTTP $CODE"; FAIL=1; continue
  fi
  if ! echo "$BODY" | grep -q "$expect"; then
    echo "FAIL $path → missing 「$expect」"; FAIL=1; continue
  fi
  echo "OK   $path"
done
exit $FAIL

呢層捉到 90% 嘅部署 regression:env var 漏、route 拆錯、SSR 拋 error。三秒搞掂。每條 path 配個關鍵字,連「回咗 200 但 render 咗一版 fallback 空白頁」嘅暗病都捉到。

3. Playwright scenario test

HTTP 層過到,唔代表用戶 flow 行得到。第三步係用 headless browser 跑 3 至 5 條最關鍵嘅 flow。

tests/smoke.spec.ts

import { test, expect } from '@playwright/test'

const BASE = process.env.PREVIEW_URL!

test('landing → CTA → signup form 開到', async ({ page }) => {
  await page.goto(BASE)
  await page.getByRole('link', { name: /開始試用/ }).click()
  await expect(page.getByRole('heading', { name: /註冊/ })).toBeVisible()
})

test('pricing page 三個 plan 都 render', async ({ page }) => {
  await page.goto(`${BASE}/pricing`)
  await expect(page.getByText('Starter')).toBeVisible()
  await expect(page.getByText('Pro')).toBeVisible()
  await expect(page.getByText('Team')).toBeVisible()
})

test('search 打字有結果', async ({ page }) => {
  await page.goto(BASE)
  await page.getByPlaceholder(/搵嘢/).fill('claude')
  await expect(page.locator('[data-testid="search-result"]').first()).toBeVisible({ timeout: 5000 })
})

跑:PREVIEW_URL=$URL npx playwright test --project=chromium tests/smoke.spec.ts

呢個唔係 full E2E,係 smoke —— 揀 3 至 5 條「呢條死咗即等於成個 site 死咗」嘅 flow。多咗會慢、會 flaky、會成日無端端報錯。

4. Slack / Discord 通知

最後一步:將上面三輪結果灌入 webhook,過到報綠、唔過報紅,再連埋 preview URL 同 commit message。

.claude/commands/ship.md

---
description: Push + verify deploy + Slack 通知
---

請執行:

1. `git push origin HEAD`
2. 跑 `.claude/scripts/wait-deploy.sh`,拎到 PREVIEW_URL
3. 跑 `.claude/scripts/smoke-paths.sh "$PREVIEW_URL"`
4. 跑 `PREVIEW_URL=$PREVIEW_URL npx playwright test tests/smoke.spec.ts`
5. 整合結果:
   - 全綠 → POST Slack webhook:✅ shipped [commit subject] → [PREVIEW_URL]
   - 任一 fail → POST Slack webhook:🔴 deploy verify FAIL → [fail 嘅 step] + [PREVIEW_URL]
6. Return 一句總結

SLACK_WEBHOOK_URL.env.local(同 Claude session env 共用)。打 /ship 一鍵搞掂:push、等、驗、報,五分鐘冇你事,行完先 ping 你。

變化

變化 1:Netlify 版本

Netlify API 同 Vercel 邏輯一樣,poll 嘅 endpoint 換做 https://api.netlify.com/api/v1/sites/$SITE_ID/deploys,state 認 readyerror。Cloudflare Pages 用 /accounts/.../pages/projects/$PROJECT/deployments,state 認 success。Step 2 至 4 完全唔使改 —— preview URL 出咗嚟之後嘅核實邏輯同邊個平台都無關,呢套例行核實嘅威力就喺度。

變化 2:三段 deploy(preview / staging / prod)

一個人開發,一段 deploy 夠用。Team / SaaS 通常分三段:feature branch 開 preview、merge develop 出 staging、merge main 出 prod。

/ship slash command 加參數:

喺 CLAUDE.md 寫明每段嘅關卡(譬如 prod 必須 staging 綠咗 24 小時先肯出),Claude 就唔會跳過 staging 直接出 prod。

變化 3:E-commerce checkout flow

賣嘢嘅 site,最關鍵嘅唔係首頁而係 checkout。Playwright smoke 必須跑足 add-to-cart → checkout → payment confirm(用 Stripe test mode key):

test('checkout flow 行到尾', async ({ page }) => {
  await page.goto(`${BASE}/products/test-sku`)
  await page.getByRole('button', { name: /加入購物車/ }).click()
  await page.goto(`${BASE}/checkout`)
  await page.getByLabel('Email').fill('smoke@example.com')
  // Stripe test card
  const card = page.frameLocator('iframe[name^="__privateStripeFrame"]')
  await card.getByPlaceholder('Card number').fill('4242 4242 4242 4242')
  await page.getByRole('button', { name: /落單/ }).click()
  await expect(page.getByText(/多謝惠顧/)).toBeVisible({ timeout: 15000 })
})

呢條 flow 一綠,等於話收錢條 pipeline 通晒。Black Friday 前一晚 push,跑完先放心瞓。

拆解:點解 work,同邊度會仆街

跟到上面就已經用得。下面呢段係畀**想由「跑一次 OK」做到「自動化值得信、出事真係捉到」**嘅人——初學者可以跳過,唔影響你跟住做。

驗證腳本最危險嘅地方係:佢自己靜靜雞死咗,但報你綠。一個成日報綠嘅 smoke test,等於冇 test,仲衰過冇——因為你會信佢。呢套流程實際會喺呢幾個位仆街,你要預咗:

1. 撈錯個 deployment(SHA 對唔上) Step 1 用 githubCommitSha 對你個 HEAD。但 Vercel 可能仲未收到 webhook、或者揀咗你之前一個 commit 嘅 deployment。jq 揀唔到就 URLSTATE 係空,STATE != READY 永遠唔成立,loop 一路行到 timeout。

2. Smoke 用 grep 認關鍵字,太鬆會永遠綠 Step 2 靠 grep -q "$expect" 認 body。問題係好多 SPA / SSR 嘅 error page、login redirect、甚至 Vercel 自己嘅 404,都係回 HTTP 200,body 入面又啱啱好撞到你個關鍵字(譬如成版都有個 nav 寫住「月費」)。

3. Playwright 對住 preview URL,但 preview 有 protection / 認證牆 Step 3 直接 page.goto(BASE)。但 Vercel preview deployment 預設可能有 Deployment Protection(Vercel Authentication),未登入會俾 401 / 重定向去登入頁;你條 selector 點都揀唔到,test 報 fail。

4. Flaky test 報紅,你慢慢就會 disable 佢 Step 3 啲 test 靠 toBeVisible、靠 timeout。網絡慢少少、cold start、Stripe iframe 慢 load,都會間中 fail。一個十次有一次無端紅嘅 smoke,比你想像中毒——因為人類對「成日嘈」嘅 alert 會自動麻木。

5. Slack 報咗綠,但 webhook 其實 POST 失敗 Step 4 POST 去 webhook。但 webhook URL 過期、Slack channel 改咗名、rate limit,POST 會靜靜失敗。最毒嘅 case 係:smoke 真係紅,但連「報紅」嗰個 POST 都失敗——你乜都收唔到,以為冇 push 過。

呢幾個位,就係「跑一次 smoke OK」同「半夜出事真係 ping 醒你」之間嘅距離。一套你信得過嘅驗證,前提係佢識報紅;一套只識報綠嘅驗證,係安慰劑。

一個心態

部署唔係「git push 完就算」,係「用戶真係見到嘅嘢同你 staging 一樣」先算。中間嗰段差距,就係靠呢套例行核實補返。

最後提醒:

下次禮拜五夜晚 push,落街食飯前打 /ship 一句。返到屋企 Slack 已經話畀你聽生定死 —— 而唔係等你啲用戶話畀你聽。

文中工具 · 連結

  • 開發者用 — terminal 入面同 Claude pair coding

  • Vercel· 免費 / 付費 plan

    Next.js 部署平台 — 呢個 site 都用緊

  • Slack· 免費 / 付費 plan

    Team chat — 多嘢 channel 嗰種

睇完想同 Claude 一齊行一次?

撳一撳,就將成段 tutor 指示(連埋成篇文嘅內容)抄入剪貼簿。 貼入 Claude.ai 或 Claude Desktop,佢會用廣東話帶你一步一步行, 每步問你填關鍵位,最後畀返一個專為你情況寫嘅 prompt 帶走。

下期預告 · 相關情境
訂閱本副刊

每週日早上,
一道新菜送到你 inbox。

一篇 use case、一個香港情境、一個跟得到嘅做法。 冇 sell course、冇話你「再唔學就會失業」。

訂閱通道執緊緊
newsletter service 仲未接通。想第一時間收到新文章——
直接 email 我哋寫一句「訂閱」就得。

Email 「訂閱」畀我