---
date: 2026-05-11
tags: [gitea, actions, ci, required-check, workflow, branch-protection]
---

# Gitea Actions 触发 / checkout / status 三件套语义——以及"required-check 修自己"死锁

PR #320（ai-review dry-run 转正）实操中连撞三个 Gitea Actions 半显语义，导致一个看似简单的转正 PR 卡了多次。把语义和绕过方法记下来，下次避坑。

## 三条事实

### 1. 触发：不同 event 用的 workflow file 来源不同

| event | workflow file 来源 | code checkout 来源 |
|---|---|---|
| `pull_request: opened/synchronize/ready_for_review` | **PR head ref** 的 workflow file | actions/checkout 默认 PR head SHA |
| `issue_comment` | **default branch**（develop）的 workflow file | 默认 default branch HEAD（除非显式 `ref:` 指 PR head） |
| `workflow_dispatch` | 调用时指定 `ref` 的 workflow file | 默认 `ref` 指定的分支 HEAD |
| `push` | push 目标分支的 workflow file | 默认推送的 SHA |

实测验证：
- PR #320 上推 commit `e0d12eb3`，因 `ai-review.yml` 只配了 `types: [opened, ready_for_review]`（无 `synchronize`），不会重跑——是 PR #259 工作流优化的副作用
- 评论 `/ai-review` 触发了 run，但 `display_title` 是 develop 的 merge commit、env 含 `AI_REVIEW_DRY_RUN: 1`（develop 老版本 yaml）、跑的也是 develop 的老 runner script——**完全没碰 PR head**
- `workflow_dispatch` 指定 `ref: feature/...` 时，确实用 feature branch 的 yaml + 代码

### 2. Status check 名字含 event 后缀，且只有 `pull_request` 触发会写

Gitea 给 commit status 写的 context 是 `<workflow> / <job> (<event>)`：

- `pull_request` 触发：`ai-review / ai-review (pull_request)` ✅
- `workflow_dispatch` 触发：**不写 commit status**（实测：run succeed，PR head 0 个 ai-review status）
- `issue_comment` 触发：写在 `head_sha` 上（issue_comment 的 head_sha = default branch HEAD，不是 PR head），不会落到 PR 上

这意味着 required check 强制是 `(pull_request)` 后缀时，所有重跑机制（手动 dispatch / 评论触发 / push 但 types 没有 synchronize）**都不能补一个有效的 status**。

### 3. "required-check 修自己"是真死锁

场景：required check 自身有 bug，修复 PR 包含该 check 的代码。

- PR 触发 → 用 PR head 的 fixed code → 跑成功 → 写 `(pull_request)` status → 满足 required ✅
- **但**如果 PR 在第一次跑时挂了（fix 还没写完 / 在后续 commit 才修），且 workflow types 没有 `synchronize` →  之后所有重试路径（评论 / dispatch）都用 default branch 的 unfixed code，且写不出 `(pull_request)` status

## 破局：临时移除 required check（A 方案）

最低破坏面的解法：

```bash
# 1. 移除问题 check
PATCH /api/v1/repos/.../branch_protections/develop
{"status_check_contexts": [<其他 check>]}  # 不含问题 check

# 2. 让团队成员（非 PR 作者）approve + merge

# 3. PR merge 后立刻把 check 加回
PATCH /api/v1/repos/.../branch_protections/develop
{"status_check_contexts": [..., "ai-review / ai-review (pull_request)"]}
```

为什么不选其他方案：
- **B（关 enable_status_check）**：所有 6 个 check 一起 bypass，范围过大
- **C（关 PR、push、开新 PR）**：丢历史 + reviewer 重新看
- **D（临时加 synchronize 进 types）**：需要 2 个 commit 改 yaml，啰嗦且把工作流污染

## 预防：以后类似 PR 怎么处理

- 修复 required check 自身 bug 的 PR：直接走 A 方案，**操作前后状态留 audit log**
- 如果 workflow 长期不接 `synchronize`，要让团队知道"修 bug 推 commit 不会重跑 ai-review"——评论 `/ai-review` 也救不了（用 develop 老代码）
- 考虑在 `ai-review.yml` 加 `reopened` 进 types（成本极低，允许通过关 PR 再开来 re-trigger）

## 关联

- PR #259（删 `synchronize`，引入这个半显语义）
- PR #320（实操踩坑的本次 PR）
- `.learnings/2026-05-11-ai-review-runner-first-pr-no-prior-comment.md`（runner pipefail bug，是这次连锁的起点）
- `.learnings/2026-05-09-gitea-actions-run-display-number-vs-api-id.md`（task_id ≠ run_id 的另一个 Gitea Actions 半显坑）
