---
name: rollback
owner: team-lead
version: 1
last-updated: 2026-04-23
---

# SOP: 回滚（发完发现有问题）

决策依据：[release-deploy-sop ADR](../../decisions/2026-04-23-release-deploy-sop.md) G 条。

## 核心原则

**PyPI 不能删版本**。所以"回滚"的真正含义是让用户跑老版本。三种产物各有各的回法：

| 产物 | 回滚方式 |
|---|---|
| PyPI 包 | 发 patch 修正版（首选）或用户手动降级 |
| ClawHub Skill 仓 | `git revert` + push 新 tag |
| epsilon 服务端 | `git checkout <老 tag>` 后重跑 `Deploy to epsilon` |
| 数据库 migration（不可逆） | `sop/services/restore.md` 从 OSS 备份恢复 |

## 决策：哪种回滚方式

```
发完坏了 → 判断：能不能快速写 fix？
    ├─ 能（< 30 分钟）→ 走 hotfix，发 patch 版（首选）
    └─ 不能 → 以下流程
```

## PyPI 包：发 patch 修正版（首选）

不是真"回滚"，是发 `X.Y.(Z+1)` 覆盖 `X.Y.Z`。

1. `git revert <坏 commit>` 或直接把 buggy 代码改回来
2. bump patch 版本（`0.4.0 → 0.4.1`）
3. 走完整 [publish.md](../services/publish.md) 链路
4. CHANGELOG 标 `### Fixed`，说明 0.4.0 有什么问题、0.4.1 修了什么

用户装 `pip install -U acong-tech-xiangqin` 会自动升级到 `.1`，version-check skill trigger 也会提示。

## PyPI 包：用户手动降级（兜底）

如果来不及发 patch：

```bash
pip install acong-tech-xiangqin==0.3.9   # 老版本
# 或
uv tool install 'acong-tech-xiangqin==0.3.9'
```

让受影响用户手动降级。**不要** yank PyPI 版本（会让已经装了的 `pip` 报错，用户一头雾水）。

## Skill 仓回滚

`acong-tech-skills/xiangqin` 仓：

```bash
git clone git@github.com:acong-tech-skills/xiangqin.git /tmp/skill
cd /tmp/skill

# 把当前 SKILL.md 回退到上个 tag 的内容
git checkout v0.3.9 -- SKILL.md
git commit -m "revert SKILL.md to v0.3.9 (0.4.0 has bug #...)"

# 打一个 revert tag（不要改 v0.4.0 tag）
git tag v0.4.1
git push --follow-tags
```

ClawHub 侧：软删不是硬删。仓库里保留历史。不建议删 GitHub repo（会破坏 ClawHub 索引）。

## epsilon 生产回滚

```
GitHub → Actions → Deploy to epsilon → Run workflow
    ↓
在 tag 输入框填老的 tag（例如 v0.3.9）
    ↓
workflow 内部 git checkout v0.3.9 + rsync + restart
    ↓
curl http://112.124.27.213/health 验证版本号
```

或本地兜底：

```bash
cd /Users/yarnb/xiangqin
git checkout v0.3.9
bash scripts/deploy.sh
git checkout main  # 回到 main，别忘了
```

## alpha 不用专门回滚

alpha 永远跟 main HEAD。main 上回滚 commit → 合 main → alpha 自动同步到回滚版本。

## 数据库 migration 回滚

**DDL 不可逆**（`DROP TABLE`、`DROP COLUMN`、`NOT NULL` 加约束）。代码层面回滚到老 tag 不会把表恢复回来。

流程：

1. 停服 `ssh root@epsilon 'systemctl stop xiangqin'`
2. 按 [restore.md](../services/restore.md) 从 OSS 最近备份恢复 `xiangqin.db`
3. epsilon deploy 老 tag（代码跟恢复的 schema 一致的那个 tag）
4. 评估损失：两次备份之间的用户动作可能丢

教训：**breaking schema change 发布前必须 OSS 有最近一次备份**。要在 `publish.md` 前置 checklist 里。

## 事后

每次回滚当天在 `brain/wiki/log.md` append：
- 触发时间 / 回滚到哪个 tag
- 根因（什么 commit 引入了 bug）
- 为什么 alpha 没抓到（alpha 验收漏了哪一步）
- 预防：alpha 验收清单加什么检查 / pytest 加什么 regression test

## 历史事故

- （初版，待事故充填）
