# Queue-Runner / Watchdog 重复审查 Bug（2026-07-02 确诊）

## 症状

Doro 不断收到同一份合同的重复"审查完毕"通知。同一天内夏阳合同被审查交付2次，家庭医生签约合同被审查交付后又有一个 thread 在跑。

## 根因

`contract-queue-watchdog`（cron `3174518affda`，每20分钟）与 `contract-queue-runner.sh` 的交互存在逻辑缺陷：

### 时间线

1. Runner 启动，从 manifest.txt 读取待处理文件列表
2. Runner 对文件A启动 `uwf thread start` + `uwf thread exec --background`
3. 背景 worker（node进程）开始跑 workflow，耗时 1-2 小时
4. Runner 自身在 `wait for worker PID` 循环中——但如果 runner 自己因某种原因退出（进程被杀、OOM、超时），只剩 worker 在跑
5. **关键 bug**：workflow 跑完后 deliverer 交付文件 + final_review 通知 Doro，但 runner 已经不在了——无法把文件从 queue/ 移入 done/
6. 20分钟后 watchdog tick：发现 `RUNNER_PID` 为空 + `DONE_CNT < TOTAL` + 无活跃 worker → **重启 runner**
7. 新 runner 读 manifest，发现文件还在 queue/（因为没被移到 done/）→ **再次启动 workflow** → 重复审查 → 重复通知

### 更隐蔽的变体（本次实证）

即使 runner 没死，也会出问题：
- Runner 在等 worker exit，worker 正常完成 → runner 移文件到 done/ → 进入下一份
- 但 runner 处理完 manifest 所有文件后正常退出
- 新文件在 runner 退出后被追加到 manifest（如 auto_notify 追加）
- Watchdog 重启 runner → runner 从头读 manifest → 前面的文件已在 done/ 会被 SKIP
- **但如果某份文件在 queue/ 中仍然存在**（不在 done/）→ 又跑一遍

### 为什么文件会在 queue/ 而不在 done/

1. Runner 异常退出，文件从未被移到 done/
2. 新追加的文件，上一轮 runner 没跑到就退出了
3. 文件被 auto_notify 或手动操作重新放回 queue/（不太可能但理论上存在）

## 缺失的防线

Runner 的 SKIP 逻辑只有一层：

```bash
[ -e "$FILE" ] || { log "SKIP (not found / already done): $BASENAME"; continue; }
```

只看文件是否还在 queue/ 目录。**完全不查 contract-tracker.json**。

## 修复方案

在 runner 的 `=== START:` 之前加 tracker 查重：

```bash
# === BEFORE START: check tracker for already-completed ===
if python3 -c "
import json, sys
t = json.load(open('$HOME/.hermes/data/contract-tracker.json'))
completed = [c['original_filename'] for c in t['contracts'] if c['status']=='completed']
sys.exit(0 if '$BASENAME' in completed else 1)
" 2>/dev/null; then
    log "SKIP (already completed in tracker): $BASENAME"
    mv "$FILE" "$QUEUE_DIR/done/"
    continue
fi
```

## 止血操作（已执行）

1. ✅ kill 了正在重复跑的 reviewer thread (06FJ5NPHT9XX63HW0WDKXV3ZSR) 的 worker
2. ✅ kill 了 queue-runner 进程 (PID 1907973)
3. ✅ 将所有已处理文件移入 done/（家庭医生签约、朱家角标识标牌、计划生育协议）
4. ✅ 验证 done/ 数量 ≥ manifest 行数 → watchdog 不会再重启 runner

## 今天的重复统计

| 合同 | 正常审查 | 重复审查 | 影响 |
|------|----------|----------|------|
| 恭兴 | 05:00 (end) | 02:45被cancel了不算 | 无重复 |
| 肃言 | 06:25 (end) | — | 无重复 |
| 卫健委 | 07:28 (end) | — | 无重复 |
| 夏阳 | 08:42 (end) | 11:40 再跑一遍 (end) | ⚠️ 重复通知 |
| 家庭医生签约 | 12:38→19:21交付 | 19:40又重启→21:16重复交付 | ⚠️ 重复通知 |

## Watchdog 今天重启 runner 的次数

今天 watchdog tick 64次，其中触发 runner 重启 **41次**（00:00-10:40每20分钟都重启一次！）。大多数重启只是空跑（文件都在 done/ 了），但恭兴和夏阳那两次重启时文件还没进 done/，导致重复审查。

## 相关组件

- Runner 脚本：`~/.hermes/skills/devops/uwf/scripts/contract-queue-runner.sh`
- Watchdog 脚本：`~/.hermes/scripts/contract-queue-watchdog.sh`
- Watchdog cron：`contract-queue-watchdog` (job_id: `3174518affda`)，每20分钟
- Queue 目录：`/tmp/contract-queue/`（manifest.txt + done/）
- Tracker：`~/.hermes/data/contract-tracker.json`
