# Gitea commit status 按 (sha, context) 后写覆盖——多 PR 共用 SHA 时会显示错误状态

**日期**: 2026-04-29
**上下文**: 优化 `quality-gates.yml` 时引入 `if: github.base_ref == 'develop'`，开了两个 PR（#166 → develop、#167 → staging）测试 if 条件，两 PR 共用同一个 head SHA `910bf00b`。

## 现象

PR #166 跑完 5 个 job 全绿，但 UI 上 4 个重型 job 显示 "skipped"。`/actions/runs/1254/jobs` API 显示 run 1254 实际全 success；`/commits/{sha}/statuses?limit=50` 显示同一 context 多条记录，最新的是 PR #167 触发的 skipped 状态。

## 根因

Gitea（与 GitHub 行为一致）的 commit status 主键是 `(commit_sha, context_name)`，**后写覆盖前写**。PR UI 读的是该 SHA 当前每个 context 的最新状态，不区分由哪个 PR/run 写入。

时间线：
1. PR #166 (→develop) 跑 run 1254 → 5 个 status 全写为 success
2. PR #167 (→staging) 在同 SHA 触发 run 1255 → verify-agent-assets 真跑写 success，4 个重型 job 因 `if:` skip 写 skipped
3. PR #166 UI 现在显示 4 个 skipped——状态被 PR #167 的 run 覆盖

## 复用方案

**测试模式（写 if 条件、验证多 base PR 行为）**：
- 不要让两个 PR 共用同一 head SHA。在测试分支推一个空 commit 改 SHA 再开第二个 PR；或者用两个独立分支。
- 已经踩坑后修复：关掉测试 PR，在原 PR 分支推 `git commit --allow-empty` 触发新 SHA，UI 状态自动刷新。

**生产工作流**：通常不会踩到——squash merge / rebase 让每层分支的 HEAD SHA 不同。但要注意「同一分支同时 PR 到两个 base」这种少见但合法的操作会导致状态显示混乱。

## 如何辨真伪

不要只看 PR UI 的 status 列。真相在：
- `GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs` —— 看具体 run 的 job conclusion
- `GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses?limit=50` —— 看该 SHA 全部 status 历史，按 `updated_at` 排序判断是哪一次 run 写的

## 测试 if 条件的最佳实践

要验证 `if: github.event_name == 'pull_request' && github.base_ref == 'X'` 在不同 base 下的行为，**用两个不同 SHA**：

```bash
git checkout -b test-if-skip
echo "test" > /tmp/test.txt   # 任何小改动
git add . && git commit -m "test"
git push origin test-if-skip
# 开 PR test-if-skip → staging
```

或者直接通过 `/actions/runs/{run_id}/jobs` API 验证 conclusion，**不要依赖 PR UI 上的 status badge**。
