Skip to content

[bug] cron 任务无内容时输出占位文本(如「(无内容)」)被投递到 IM,造成刷屏 #2156

Open

Description

@kajhg

问题描述

cron 定时任务在「无异常 / 无新增内容」时,模型输出的占位文本会被原样投递到 IM 通道(飞书 / 企业微信 / 微信),造成刷屏。

现象

多 agent 场景下,巡检 / 扫描类定时任务在「无事可报」时,用户手机收到大量占位气泡:

  • 「(无内容)」
  • 「(本轮不发消息)」
  • 「(无输出)」

实测:单个任务每 30 分钟一次,全天数十条空消息刷屏,多任务叠加更严重。

根因

投递层(octop/infra/cron/delivery.py)只把「完全空」当作静默:

outbound = strip_thinking("".join(parts)).strip()
if not outbound:
    raise RuntimeError("cron agent run produced no visible response")

但模型在「无事」时输出的往往不是空字符串,而是「(无内容)」这类非空占位文本,于是被当作有效输出投递。

更深一层:为什么模型会输出占位文本

为了让模型「无事静默」,常见的提示词写法是「正文一律为空——不要写任何字」。但模型会复述这句话本身(把「不要写任何字」当成正文输出),于是冒出「(无内容)」「(本轮不发消息)」这类占位话术。

这形成了死循环:提示词越强调「不要输出」,模型越容易输出「不输出」的自述。

复现步骤

  1. 建一个「巡检 / 扫描」类 cron 任务,提示语里写「无异常时不发消息 / 正文留空」之类
  2. 让它跑到「无异常」分支
  3. 观察 IM 通道:收到占位气泡,而不是静默

期望行为

  • 模型「无事可报」时,投递层能识别并丢弃占位文本,真正静默
  • 或提供规范的「静默」机制(例如模型输出特定标记表示「本轮不投递」,投递层据此跳过)

环境

  • Octop 版本:v1.0.2b9
  • 通道:飞书 / 企业微信 / 微信
  • 部署:Linux systemd,12 个 agent

建议

  1. 投递层增加「占位输出过滤」:识别并丢弃「纯括号包裹 / 纯占位词 / 去空白后字数极少」的输出
  2. 或提供「静默信号」机制,让模型能明确表达「本轮不投递」,从协议层面解决

Activity

  1. jubaoliang commented on Oct 17, 2026

    @jubaoliang
    Collaborator

    这个需要评估一下

  2. balabommablock-cpu commented on Oct 18, 2026

    @balabommablock-cpu

    One detail in cron/delivery.py makes this worse: an empty reply raises cron agent run produced no visible response, so a job that correctly says nothing gets recorded as a failure. The model has no successful way to stay quiet, so it writes "(无内容)" instead.

    A simpler fix than guessing which short texts are placeholders: give "nothing to report" one exact form. For example, if the whole reply (after strip_thinking) is [SILENT], the run counts as a success, nothing is delivered, and the run history says "nothing to send". A scan prompt can then just say "reply [SILENT] if nothing changed."

    Empty replies can stay errors, which still catches broken runs. A fuzzy filter would also swallow real short answers like "无" or "OK", and a missed alert is worse than one extra message.

  3. kajhg commented on Oct 18, 2026

    @kajhg
    Author

    实测结果(供验证参考)

    我们在生产环境(v1.0.2b9,Linux systemd)对 [SILENT] 静默机制做了两轮实测,结果如下,供后续版本参考:

    1. task_type=agent + 回复 [SILENT] → 静默生效

    提示语要求模型在无事项时只回复 [SILENT],投递层日志:

    octop.infra.cron.delivery — cron run silent marker: name=<job> agent=VQCHFC
    

    任务 last_status="ok"、last_error=null,无消息投递。符合预期。

    2. task_type=text + 推送内容含 [SILENT] → 通道层过滤兜底生效

    text 型 cron 不走 delivery.py 的静默分支(agent 型专用),但我们在线上做了输出侧过滤补丁(hermes-patch: quiet-output-filter,octop_gateway/channel.py),覆盖三条出站路径:

    • reply_text / push_text:quiet_output_reason(text) 命中即丢弃
    • reply_content / push_message(富文本 parts):_parts_are_placeholder() 对纯文本 parts 拼接后走同一签名判断;带媒体附件不拦

    实测日志:

    octop_gateway.channel — quiet-output filtered (push): reason=signature:本轮无 chars=32
    

    消息未投递,任务状态 ok。

    3. 富文本路径(push_message / reply_content,即 POST parts 推送)→ 同样被拦

    text-only parts(TextContent 列表)在入口处经 _parts_are_placeholder() 判断:把所有文本 part 拼接后走同一个 quiet_output_reason() 签名匹配,命中即整条丢弃(日志 quiet-output filtered (push_message)),不占限流槽;列表中含媒体附件(图片/文件)时不拦,避免误杀正常图文消息。

    代码路径:octop_gateway/channel.py — reply_text(L407) / push_text(L484) / _parts_are_placeholder(L497),reply_content(L417) 与 push_message(L468) 均在发送前先过该判断。text 型与富文本型共用同一签名函数,无遗漏路径。

    结论

    • [SILENT] 方案在生产可用(agent 型)
    • text 型任务建议官方也纳入静默判断(当前 delivery.py 只对 agent 型生效,text 型原样投递,需通道层过滤兜底)
    • 富文本(POST parts)路径已由同一签名过滤覆盖,纯文本占位可拦、带媒体不拦
    • 期望:官方在后续版本把静默判断同时应用到 text 型投递路径
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions