Skip to content

fix(engine): 错过任务检测漏查时间规则,00:00 任务永远触发不到 - #46

Open
AmberCXX wants to merge 2 commits into
LinekForge:mainfrom
AmberCXX:fix/engine-missed-task-detection
Open

fix(engine): 错过任务检测漏查时间规则,00:00 任务永远触发不到#46
AmberCXX wants to merge 2 commits into
LinekForge:mainfrom
AmberCXX:fix/engine-missed-task-detection

Conversation

@AmberCXX

Copy link
Copy Markdown
Contributor

动机

生产环境观察到两个现象:

  1. 「每周日 22:00」的任务在周六被报成「今天错过」——收到 [错过的任务] 通知,但该任务当天根本不该跑
  2. 排在 00:00 的任务从未按时触发过——翻 engine-trigger-log,某个 00:00 任务历史上 4 次触发全在下午(16:29 / 17:11 / 17:27),都是被别的 reload 顺带唤醒的,没有一次在 00:00

根因是同一处:scheduleOrigin() 建定时器时走 canScheduleToday(),而它只查 start_date / end_date;完整时间规则在 shouldFire() 里,只在 fire() 时才调用(scheduler.ts:241)。

于是排期阶段:

  • weekdays/days/months 不参与 → 非当天的任务也会进 missed 列表
  • delay <= 0 一律进 missed → 午夜重排在 00:00 跑时,target - now 恰为负,00:00 的格子被自己判成已过去

改动概要

forge-engine/scheduler.ts

  • 新增 FIRE_GRACE_MS = 90 * 1000
  • scheduleOrigin() 增加宽限支:delay 落在 (-FIRE_GRACE_MS, 0] 且通过 shouldFire() → 立即补触发(1s 后),计入 count,不报错过
  • missed 支补 && shouldFire(entry)
  • shouldFire 改为 export(仅为可测性,无行为变化)

forge-engine/scheduler.test.ts

  • 新增 3 个回归测试覆盖 weekdays / days / 无条件默认

影响范围

  • forge-engine 排期路径,不涉及 hub-server / 通道 / 审批
  • 行为变化有二:① 刚过点 90s 内的任务会被补触发(此前静默丢失)② 不该在今天跑的任务不再出现在「错过的任务」通知里
  • FIRE_GRACE_MS 取 90s 是保守值:足以覆盖午夜重排与冷启动的秒级偏差,又远小于最密的任务间隔(本地为 1 小时)。如需可配我可以改成常量导出或读 config

self-test 结果

$ bun hub-test-harness/harness.ts
📊 8/8 通过

$ fh hub self-test
📊 8/8 通过

$ bun test          # forge-engine
22 pass, 0 fail, 76 expect() calls

安全自检

$ grep -rE '@im\.wechat|ou_[a-f0-9]{16,}|sk-ant-[a-zA-Z0-9_-]{20,}|sk-[a-zA-Z0-9_-]{20,}|[0-9]{9,10}:[A-Za-z0-9_-]{35}' forge-engine/scheduler.ts forge-engine/scheduler.test.ts
(无输出)

无私人 ID / 密钥;测试用例不含真实 sender_id。

未覆盖

FIRE_GRACE_MS 那条宽限支没有专门的单元测试——它在 scheduleOrigin() 内,需要 mock Server 才能测。本地是通过日志验证的:把某任务从 00:00 改到 22:00 后重排显示 排定 1,而同一任务在 00:00 时显示 排定 0。如果你希望补这个测试,告诉我要不要把 scheduleOrigin 拆得更可测,我再提一版。

🤖 Generated with Claude Code

https://claude.ai/code/session_016fd3qWDATPyaLxwM8s1Ag9

AmberCXX and others added 2 commits August 22, 2026 00:51
scheduleOrigin 建定时器时只过 canScheduleToday(仅查 start_date/end_date),
未过 shouldFire,导致两个问题:

1. weekdays/days/months 不参与判定——「每周日」的任务会在周六被
   报成「今天错过」
2. delay <= 0 一律落进 missed 分支——午夜重排本身发生在 00:00,
   重排那一刻就把 00:00 的格子算成已过去,该时段任务永远跑不到

修法:
- 新增 FIRE_GRACE_MS(90s)宽限支,刚过点的任务立即补触发而非报错过
- missed 与宽限两支都补 shouldFire(entry)

shouldFire 改为 export 以便测试,并补 3 个回归测试。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fd3qWDATPyaLxwM8s1Ag9
错过判定原本只看两样:delay 落在 MISSED_WINDOW_MS(2h) 内,以及 shouldFire
的排期规则。它从不查该条目今天是否已经触发过——fire() 只写 global.json 的
last_fire / last_sender / today_count 这类全局标量,回答不了「这一条今天跑没跑」。

于是任务时刻之后 2 小时内的任何一次重排,都会把已经跑完的任务重新报一遍:
启动、配置热加载的部分重排,以及午夜重排在边界上提前几毫秒跑到前一天时
(此时 22:00 的 delay 约为 -1h59m,正落在窗口里)。2026-08-22 / 08-23 /
08-28 / 08-29 连续复发,每次都要人工判真假,判错的代价是重复推送或漏跑。

改动:新增 per-entry 的已触发记录(forge-state/fires.json)
- fireKey(entry)  来源文件 + 时刻 + 名字(缺省回退 sender)作稳定标识
- hasFiredToday(state, entry, today)  纯函数,state 由调用方注入
- markFired(state, entry, today)  返回新 state,并清掉非当天的键,文件不增长
- fire() 成功推送后落一条记录;scheduleOrigin 每次重排只读一次状态文件
- 错过分支追加 !hasFiredToday(...)

真实的漏跑不受影响:engine 当时没跑就没有记录,照常上报。

验证:forge-engine 28 tests 全过(新增 6 条覆盖键稳定性、label 缺省回退、
跨日失效、清理旧键、同日幂等);bunx tsc --noEmit 无错;
bun hub-test-harness/harness.ts 8/8;fh hub self-test 8/8。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWhZ8wD8Ne5yiYaCdL6cjf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant