# Queue-Runner + Watchdog 重复审查Bug(2026-07-02 实证) ## 现象 Doro反复收到同一份合同的"审查完毕"通知。今天受影响的合同: - 夏阳合同:被通知至少2次(可能3次) - 家庭医生签约合同:被通知2次(第3次在final_review被手动杀掉) ## Bug链条(已验证) ``` 1. Queue-runner 启动 → START 合同X → 创建 worker PID → "Waiting for worker..." 2. Runner 进程异常退出(OOM/信号/shell被杀),但 worker 子进程继续运行 3. Worker 独立完成整个 workflow(包括 final_review = 私信通知 Doro) 4. Runner 已死 → 没有执行 "mv $FILE done/" → 文件仍在 queue 目录 5. Watchdog(20min cron)检测到:runner不在 + done/ < manifest → 重启 runner 6. 新 runner 看到文件还在 queue → SKIP逻辑只检查 `[ -e "$FILE" ]` → 认为未处理 7. 新 runner 启动第二个 thread → 从头审查 → final_review 又通知 Doro ``` ## 额外失败模式(watchdog恢复旧thread) ``` watchdog.log: [2026-07-02 18:40:14] suspended 06FJ42KNSK1ZA767W8AZ6MXJ6C → exec 恢复 [2026-07-02 18:40:15] idle 06FJ3ZJXR5WHJNPDHF22YYMGCM → exec 续跑 ``` Watchdog 恢复处于 idle/suspended 状态的旧 thread,这些 thread 的同名合同可能已被新 runner 完成。 旧 thread 被唤醒后接着跑完 final_review → 又一次通知。 ## 今天的实际时间线 | 时间(BJT) | 事件 | |-----------|------| | 02:45 | 第一个runner启动,处理恭兴(cancelled) | | 05:00 | Watchdog重启runner → 恭兴(成功) | | 06:25 | 肃言完成 | | 07:28 | 卫健委完成 | | 08:42 | 夏阳 thread#1 启动 (06FJ3ZJXR) | | ~11:00 | 夏阳#1 完成(含final_review通知);但runner已死,文件没进done/ | | 11:40 | Watchdog重启runner → 夏阳 thread#2 启动 (06FJ58AD) | | 12:38 | 夏阳#2 完成(第二次通知);家庭医生签约 thread 启动 (06FJ5NPHT) | | 18:40 | Watchdog恢复旧idle thread 06FJ3ZJXR5WHJNPDHF22YYMGCM (夏阳#1) + 06FJ42KNSK (家庭医生#?) | | 19:21 | 家庭医生签约交付到Nextcloud | | ~21:00 | 06FJ42KNSK 完成 final_review(第2次家庭医生通知) | | 21:16 | Watchdog再次重启runner → 家庭医生 thread#2 (06FJ5NPHT) 进入 final_review | | 21:30 | 手动 kill -9 杀掉 06FJ5NPHT 的 final_review → 阻止第3次通知 | ## 止血SOP 当 Doro 报告收到重复通知时: 1. **找重复进程**:`ps aux | grep -E "(background-worker|uwf-hermes)" | grep -v grep` 2. **杀掉重复进程**:`kill -9 ` 3. **杀掉queue-runner**:`kill ` 4. **清理queue**:把所有tracker中已completed的文件移入done/ ```python import json, os, shutil tracker = json.load(open(os.path.expanduser('~/.hermes/data/contract-tracker.json'))) completed = {c['original_filename'] for c in tracker['contracts'] if c['status'] == 'completed'} queue_dir = '/tmp/contract-queue' for f in os.listdir(queue_dir): if f.endswith(('.doc', '.docx', '.pdf')) and f in completed: shutil.move(f'{queue_dir}/{f}', f'{queue_dir}/done/{f}') ``` 5. **验证**:`find /tmp/contract-queue/ -maxdepth 1 -name '*.doc*'` 应为空 6. **确认watchdog不会重启**:done/ 文件数 ≥ manifest.txt 行数 ## 缺失防线(待修复) | 位置 | 应加的检查 | |------|-----------| | queue-runner START 逻辑 | 启动workflow前查tracker:已completed直接mv到done/ | | watchdog 恢复thread逻辑 | resume前查:同filename的tracker记录是否already completed | | final_review | 发通知前查:是否24h内已有同合同的通知(防御性去重) | ## 关键文件路径 - Queue runner: `/home/maggie/.hermes/skills/devops/uwf/scripts/contract-queue-runner.sh` - Watchdog: `~/.hermes/scripts/contract-queue-watchdog.sh` - Queue dir: `/tmp/contract-queue/` (manifest.txt + done/) - Queue log: `/tmp/contract-queue/queue.log` - Watchdog log: `/tmp/contract-queue/watchdog.log` - Tracker: `~/.hermes/data/contract-tracker.json` - Cron jobs: `contract-queue-watchdog` (*/20, job_id:3174518affda), `auto-notify-watchdog` (5min, job_id:63bb31d4f050)