## [ERR-20260519-005] auto_merge_daemon_stale_needs_rebase_label

**Logged**: 2026-05-19T17:50:00Z
**Priority**: high
**Status**: resolved
**Area**: infra

### Summary

auto-merge daemon 自动 `update_branch` 撞 conflict（HTTP 409）→ 给 PR 加 `needs-manual-rebase` label → evaluate 看到 label 直接 skip。人工去 slot 跑 `git rebase + 解冲突 + push --force-with-lease` 后 PR mergeable 回 True，**但 label 还在**——daemon 继续 skip。PR 卡住直到人工去 Gitea web UI 手动删 label。

实际撞例（PR #454）：17:10 daemon 加 label → 17:33 人工 rebase 完 → 17:40 daemon tick 仍 skip（"含 label needs-manual-rebase"）→ 17:43 人工删 label → 17:45 下次 tick daemon 合。**卡了 35 分钟纯人工流程瓶颈**，且认知负担"为什么 mergeable=True + CI 全绿还不合"特别隐蔽。

### Error

daemon log（runner-1）：

```
17:10:03Z update_conflict pr=454 response={"message":"merge failed because of conflict"}
17:40:02Z #454 ... — skip: 含 label 'needs-manual-rebase'（上次 update 失败，需人工处理）
```

PR API：

```json
{"state":"open","mergeable":true,"labels":[{"name":"needs-manual-rebase"}]}
```

CI 全绿 + AIBot verdict=pass + 无 REQUEST_CHANGES + mergeable=True，**但 label 在** → daemon 永远跳。

### Context

- 触发链：daemon update_branch 失败 → 加 label（line 360-376）→ evaluate skip（line 194-195）→ 人工解 conflict → label 没被任何机制清掉
- daemon 设计假设：人工解 conflict 后**也会去 Gitea UI 手动删 label**——但这个隐性契约没写在任何地方，AI / 人都会忘
- 跟 ERR-20260519-004（daemon outdated 不检测）是连环坑——004 是 daemon 没主动 update，005 是 daemon update 失败留 stale 信号

### Suggested Fix（已实施）

**根因层**：人工解冲突 ≠ daemon 自动感知。把"清 label"这事从隐性人工动作变成 daemon 显式自检——把"是否要清"问题塞进 evaluate 入口。

`scripts/ops/auto-merge-develop.py` `evaluate()` 改动（line 194-205）：

```python
if has_label(pr, LABEL_NEEDS_REBASE):
    # mergeable=True 表示人工 rebase + force-push 已解了 conflict → 自动清 stale label
    if pr.get("mergeable") is True and clear_needs_rebase_label(api, pr):
        log_event("cleared_stale_needs_rebase_label", pr=pr["number"])
        # 继续往下走 evaluate
    else:
        return ACT_SKIP, f"skip: 含 label '{LABEL_NEEDS_REBASE}' (mergeable={pr.get('mergeable')!r}, 等人工处理)"
```

判据为何用 `mergeable=True` 而不是 `base_sha == merge_base`：
- label 语义是"daemon update_branch 出真 **conflict**"
- conflict 解决后 mergeable 一定 True（无 conflict）
- 即使 outdated 还存在，evaluate 下游会走 ACT_UPDATE 让 daemon 重新 update_branch；这时新一轮 update 又可能失败，但 daemon 会重新加 label，状态机自洽

### Metadata

- Reproducible: yes（造真 conflict + 人工 rebase 不删 label 必现）
- Related Files:
  - scripts/ops/auto-merge-develop.py（evaluate + 新 helper clear_needs_rebase_label）
- See Also:
  - ERR-20260519-004（daemon outdated 不检测）—— 连环根因，004 让 daemon 开始主动 update，005 修 update 失败后的"清场"链路
  - ERR-20260514-001（agent-pool sweep zombie 误识别）—— 同模式：自动化系统加 stale 状态信号后没自动清，靠人工去清不可持续

### Generalization

**任何"自动化系统打 stale 信号 label / flag" 必须有对应的"自动清"机制**——人工动作做完那一刻没法触发清场，必须靠系统下次 tick 自检"信号还有效吗"。设计 daemon / cron 自动化时凡引入 mutable state（label / lock / flag），都该问：清除这个 state 的触发器是谁？如果"靠人工"，就必然产生这个 ERR-005 模式的卡顿。

---
