Agent 回滚不彻底:6 个域 + 按域回滚 checklist

代码回了,但 agent 漏了:过期 `dist/`、已应用的 migration、未关的 feature flag、第三方 dashboard 的改动——按域回滚,不要靠 `git reset`。

你让 agent 把过去一小时的活儿撤了。它跑了 git reset --hard HEAD~3,报告「已回滚」。代码确实回来了,但是:数据库还多着 20 分钟前 migration 加的那个列;dist/ 里还是旧 bundle、Vercel 已经部上线了;Stripe webhook URL 在 dashboard 里被改过指到一个已经不存在的路径;PostHog 里 BILLING_V2_ENABLED 还开着。

「回滚」只撤 git 里有的——其他一切(生成产物、DB 状态、第三方配置、环境变量、feature flag、缓存)各有自己的生命周期。完整回滚是一份按域 checklist,不是一条 git 命令。下面拆解 agent 通常漏的 6 个域,加能 catch 的 prompt。

常见原因

按命中率从高到低:

1. Agent 只回了 git tracked 的代码

git reset --hard 回代码,其他都不动。Agent 自信地说”已回滚”——它眼里的”状态”是 repo,不是系统。

如何判断:回滚完磁盘上代码是目标 commit,但跑起来表现还是新代码——某处的输出/状态 ≠ 代码。

2. DB migration 已应用且非幂等

新 feature 加了列,agent 跑了 migration。现在代码回了但列还在——老代码读/写它不认识的 schema 可能直接挂。

如何判断pnpm db:statusprisma migrate status 显示 migration 已应用。或老代码的 model 定义和在线 DB schema 不一致。

3. dist/.next/.svelte-kit/ 产物没重建

代码回了,但 dev server 还在服务缓存的编译产物——浏览器看到的是新行为。更糟:你的部署管道把 dist/ 推到了 CDN,CDN 还在服务它。

如何判断:代码 X、浏览器 Y。ls -la dist/ 文件比最新代码 commit 还新。

4. 第三方配置改了没回

新 feature 要改 Stripe webhook URL、Google OAuth redirect URI、Cloudflare DNS——这些完全在 repo 外。Agent 根本不知道。

如何判断:回滚后外部集成挂——因为它们在 call 已经不存在于回滚代码里的 URL。

5. 环境变量 / feature flag 还开着

NEW_FEATURE_ENABLED=true 还在 .env.production 或 PostHog/LaunchDarkly。新代码没了,flag 要么没用、要么被老代码读到崩——因为老代码不认识这个 flag。

如何判断vercel env ls(或你平台对应命令)有的变量在回滚后的 .env.example 里找不到。

6. 缓存(CDN / Redis / 浏览器)还有新响应

Cloudflare 缓存了新 API 响应 1 小时;Redis 缓存了新计算值 24 小时。代码回了,用户在 cache 过期前还是看新数据。

如何判断:硬刷新 / 换浏览器 / curl 看到老的,但普通浏览器看新的——cache 层是差异。

最短修复路径

按收益从高到低。Step 1 是框架,2-6 是按域回滚。

Step 1:列出原改动触达的每个域

回滚前先做”blast radius 清单”:

为最近 3 个 commit,列出受影响的每个域:
1. Git 代码:哪些文件
2. 构建产物:哪些 dist/build 目录
3. DB:哪些 migration 应用了、什么 schema 变化
4. 环境变量:哪个 env 加/改了什么
5. Feature flag:哪个工具里新建/翻转了什么
6. 第三方配置:Stripe/OAuth/DNS 等的具体改动
7. 缓存:CDN/Redis/浏览器哪层可能还存着新状态

每个域给出精确回滚动作 + 对应命令。

你立刻能看出哪些域得在 git revert 之外另外处理。

Step 2:用 git revert,不要 git reset --hard

git reset --hard 破坏性,同分支别人的活儿会丢——用 revert 它新建反向 commit:

# 反向 revert 最近 3 个 commit
git revert HEAD~2..HEAD --no-edit
git push origin HEAD

然后部署反向后的代码让生产跟上。

Step 3:回滚 DB migration

migration 是非破坏性的(只加列/表)——老代码可能还能跑,schema 不动、停止使用即可。破坏性的(改名 / 删列)需要显式 down migration:

# Prisma
pnpm prisma migrate resolve --rolled-back <migration_name>
# 再建一个反向 migration
pnpm prisma migrate dev --name revert_<original_name>

# Drizzle / 原生 SQL
pnpm db:rollback   # 如果框架有
# 没有就手写 undo migration

有 migration 前的 DB 备份的话,从备份恢复有时比手写 down migration 简单。

Step 4:重建并重部产物

# 本地
rm -rf dist .next .svelte-kit node_modules/.cache
pnpm install
pnpm build

# 推
vercel deploy --prod
# 或触发 CI 重部

# 清 CDN
curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone>/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -d '{"purge_everything":true}'

别信”部署会自动 pick up revert”——强制干净重建 + cache purge。

Step 5:回第三方配置和 env 变量

每个外部系统走一遍,照着 checklist:

- Stripe webhook URL:https://app.example.com/api/webhooks/billing(原 /api/v2/webhook)
- Google OAuth redirect:https://app.example.com/auth/callback
- DNS:api.example.com CNAME
- PostHog flag BILLING_V2_ENABLED:关
- Vercel env vars 新增:BILLING_V2_API_KEY 删
- Cron 调度:hourly billing-sweep 停

这些手动回滚是 agent 漏得最多的部分。团队用 Terraform/Pulumi 管这块的话,git revert + terraform apply 就够;否则得在 dashboard 里点。

Step 6:必要时强制清缓存

# Cloudflare 全清
wrangler cache purge --zone <zone-id>

# Redis 特定 key(或 dev 直接 flush)
redis-cli --scan --pattern "billing:v2:*" | xargs redis-cli del

# 浏览器:让用户硬刷新,或在 HTML 里 bump 资源版本号

面向用户的 cache,TTL 短(分钟级)就等它自然过期;TTL 长才强制 invalidate。

预防建议

  • 任何跨域风险改动前先做”rollback inventory”——每个可能被动的域都快照
  • migration 做幂等 + 可逆——每个 up 都有测过的 down
  • 风险发布用 feature flag——秒级关 flag,不需要回代码
  • 第三方配置走 IaC(Terraform / Pulumi)——回滚 = git 操作
  • 每个 feature 写「回滚 runbook」——agent 和人都按它走
  • 不要把 git reset --hard "rolled back" 当完整答案——每个域再核一次

相关阅读

标签: #排查 #Claude Code #排查 #回滚