Skip to content

plugin-email: sys_email 的 queued 行崩溃后永久滞留 —— 无任何轮询者,且 drain 钩子把失败 warn 掉 #5161

Description

@os-zhuang

Blocked-by: #5160(同文件 email-plugin.ts,严格串行;且 #5160 落地后本单的正确修法才定形 —— 清扫出的行在队列模式下应交给队列,而不是自己直接投)

PM 在回答维护者「邮件有没有队列」时读码发现,维护者已确认要修。

现状

sys_email 的 status: 'queued' 行只有一个消费时机:insert 当时的 afterInsert drain 钩子(email-plugin.ts ~390,setTimeout(0) 异步自投)。全仓 grep 过:没有任何定时任务、启动清扫或 worker 会再看一眼 queued 行。

两个后果:

  1. 崩溃窗口:进程在 insert 之后、投递完成之前死掉,该行永远停在 queued。一个叫 queued 的状态没有队列在消费它 —— declared ≠ delivered 的又一实例,只是这次声明的是状态词。
  2. 失败静默:drain 钩子的两层 catch 都只 logger.warn 就吞掉(outbox drain failed for … / outbox drain hook error)。按 AGENTS.md degradation-log-level 标准,这是「投递没有发生」级别的失败,应为 error 且带后果与修复;warn 会被启动日志淹没。

建议

  1. kernel:ready 时做一次启动清扫:捞 status='queued' 且非本进程 managed 的行 → 队列模式下 publish 给 email.send.async(plugin-email: 邮件投递接入持久化队列 —— send 走 email.send.async / sys_job_queue(重试+DLQ),可配置开关 #5160 的通道),内联模式下 deliverPersistedRow;清扫结果(捞了几行、成败各几)打 info 一行。
  2. 是否加周期性清扫由实现判断 —— 若 plugin-email: 邮件投递接入持久化队列 —— send 走 email.send.async / sys_job_queue(重试+DLQ),可配置开关 #5160 的队列模式已覆盖新写入的行,周期清扫可能只对内联模式有意义,不要为覆盖率造第二套调度。
  3. drain 钩子的失败升 error,消息含后果(这封信没有发出,行停在 queued)与修复(重试入口/清扫会捞)。

验收

  • 人为构造「insert 后进程死亡」的行(直接插 queued 行不触发钩子即可模拟),重启后该行被推进到 sent/failed,不再永久滞留;
  • drain 失败的日志级别与文案有用例钉住;
  • 默认路径(正常 insert→投递)行为不变。

Activity

  1. self-assigned this
    on Aug 4, 2026
  2. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 6 轮
    会话:session_017MCKJaEomEqg4tvz4SzdNd
    分支:claude/issue-5161-sys-email-queued-sweep
    Worktree:objectstack-issue-5161
    域:domain:services
    文件面:packages/plugins/plugin-email/src/email-plugin.ts、packages/plugins/plugin-email/src/email-service.ts(如需)、同包测试(越界即停,报告说明)

    Blocked-by 已解除:#5160 的 PR #5173 已合入(9c4f1743c),落地判据核过完好。与 #5179 并批(service-queue 包,文件面不相交)。注意 #5177 排在本单之后、会碰同包的 email-service.ts —— 本单不要顺手动 headers/attachments 相关的任何东西。


    Generated by Claude Code

  3. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    复核结论:ACCEPT —— PR #5191,转 ready 并入合并队列。最终 head 13464b40(门禁修复)上 23 个 check run 全部 success/skipped。

    实现复核(对 diff 独立核过)

    门禁往返一次(今日第三例,assertEngineDeleteDispatch,已记 #5197),修复与 b169f217 同形。

    至此 #5160 的两个直接后续全部落地在途:本单(sys_email 侧)与 #5179(sys_job_queue 侧,PR #5192 已入队)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions