# [LEARN-20260501-001] CI 检查 silent fail-open：浅 fetch + `2>/dev/null` 吞错误

## 触发场景

PR #207 提交了**两个 migration 目录**，违反 CLAUDE.md 数据库规则
"一个 PR 最多一个迁移目录"。CI 的 `migration-file-count` 检查报告
`New migration directories: 0 ✅ migration count check passed`——**通过了**。

是用户人工 review 才发现，否则坏 schema 会被合进 develop。

## 根因

CI 脚本：

```yaml
- name: Checkout
  uses: actions/checkout@v4
  with:
    fetch-depth: 0   # ← 拉完整历史

- name: Check at most one new migration directory
  run: |
    git fetch origin "$BASE_REF" --depth=1 || true   # ← 又把历史砍到 1 commit
    CHANGED=$(git diff --name-only --diff-filter=A "origin/${BASE_REF}...HEAD" 2>/dev/null || true)
    # 三点语法靠 merge-base，浅历史里找不到 → git diff 报错
    # 2>/dev/null || true 吞掉错误
    # CHANGED = ""
    # COUNT = 0
    # 永远绿
```

**三个失误叠加**：

1. **冗余的 `--depth=1` fetch** 破坏了 checkout 已经准备好的完整历史
2. **`origin/develop...HEAD` 三点语法**依赖 merge-base，浅历史下算不出来
3. **`2>/dev/null || true` 把错误吞掉**——本应 fail-fast 的检查变成 fail-open

## 修法

```yaml
- name: Check ...
  run: |
    BASE_REF="${GITHUB_BASE_REF:-develop}"

    # 显式可达性校验，fail-fast
    if ! git rev-parse --verify "origin/${BASE_REF}" >/dev/null 2>&1; then
      echo "❌ origin/${BASE_REF} not found locally; checkout step needs fetch-depth: 0"
      exit 1
    fi

    CHANGED=$(git diff --name-only --diff-filter=A "origin/${BASE_REF}...HEAD")
    # ↑ 不再吞错误；如果 git diff 失败就让 step 直接挂
    ...
```

## 通用规则（适用所有 CI 检查）

1. **不要给已 `fetch-depth: 0` 的 checkout 再 `git fetch --depth=1`**——会把完整历史砍掉
2. **不要在关键检查上用 `2>/dev/null || true` 吞错误**——会把 fail 变成 fail-open
3. **关键检查应有显式 sanity check**：例如 `if ! git rev-parse --verify "$ref"` 提前 fail-fast
4. **写完 CI 检查后必须负向测试**：构造一个**应该 fail 的 PR**（如 2 个迁移、缺 env、break contract），验证 CI 真的红
   - 我们的 migration-count 检查从加进来到现在没人构造过反例验证 → silent 失效

## 后续行动

1. ✅ PR 207 修了 CI 检查 + 合并两个迁移
2. ⏳ 同步 grep 其他 CI 步骤里的 `2>/dev/null || true` + `--depth=1` 模式：
   ```bash
   grep -rn '2>/dev/null *|| *true\|--depth=1' .gitea/workflows/
   ```
3. ⏳ 加项目 `.gitea/workflows/README.md` 写明这两条 CI 红线，未来 review 卡死

## 价值评级

**high**——这是项目级 CI 基础设施 bug，不修会让所有"形如 1 个迁移文件"的硬指标
检查同样 silent 失效。下次有人做 schema 大改、撞同一规则，CI 又会假绿——而合进
develop 的多迁移文件就是 fresh-replay 卡断点的源头（参考
`docs/todos/migration-history-debt.md`）。
