sanitize 的关键词误杀:别用正则在全量回复里"抓错误"
OpenClaw 又被报了一个让人无语的 bug(#12309):用户问 Agent "什么是 context overflow",Agent 在 WebChat 里正常回复了一段技术解释。但同一条回复发到飞书频道时,内容被替换成了:
Context overflow: prompt too large for the model. Try again with less input or a larger-context model.
Agent 没有遇到任何错误。它在正常回答用户的技术问题。但因为回复里包含了 "context overflow" 这个关键词,被 OpenClaw 的消息后处理逻辑当成了真的错误,直接替换掉了。
同一条消息,两个渠道,两种结果
WebChat 的消息直接从 Agent 事件流传递,不经过 sanitizeUserFacingText()。外部渠道(飞书、Telegram、Slack 等)的消息走 createReplyDispatcher → normalizeReplyPayload → sanitizeUserFacingText()。
sanitizeUserFacingText() 里调了 isContextOverflowError()。这个函数用正则匹配文本内容,只要文本里出现 "context overflow"、"timeout"、"timed out"、"maximum context length"、"prompt is too long" 这些关键词,就判定为错误消息,然后用预设的错误提示替换原文。
问题在于:它匹配的是全文,不区分"这是错误消息"还是"这是在讨论错误消息"。
哪些关键词会被误杀
#12309 列出了容易踩雷的关键词:
| 关键词 | 正常对话中出现的概率 | 场景 |
|---|---|---|
| context overflow | 高 | 讨论 LLM 技术细节 |
| timeout | 高 | 讨论网络、编程 |
| timed out | 高 | 讨论网络、编程 |
| overloaded | 中 | 讨论服务器负载 |
| rate limit | 中 | 讨论 API 使用 |
如果你的 Agent 是用来做技术问答的——这恰恰是 OpenClaw 用户的主流场景——这些词几乎天天出现在正常对话里。
你让 Agent 解释"怎么处理 API rate limit",它回复了一段技术方案,发到 Telegram 上变成了"Rate limit exceeded, please try again later"。你让 Agent 帮你排查超时问题,回复里有 "timed out" 字样,到了飞书上变成一条错误提示。
根本问题:在文本层面做错误检测
用正则在全量回复文本里扫关键词来判断"这是不是错误消息",本身就是一个有缺陷的方案。
Agent 的输出文本和 Agent 的执行状态是两回事。Agent 可能在正常回复里提到各种"像错误"的词(因为用户问的就是技术问题),也可能在遇到真正的错误时输出看起来很正常的文本("我无法完成这个任务,但这里有一些替代方案")。
正确的做法是通过结构化的元数据判断错误,而不是扫描文本内容。
Agent 运行时已经有 stopReason 字段。如果 Agent 因为错误而停止,stopReason 会是 "error"。真正的错误应该由 formatAssistantErrorText() 处理,这个函数会先检查 stopReason === "error",确认确实出了问题,再用错误模式去匹配具体类型。
sanitizeUserFacingText() 不检查 stopReason。它对所有出站消息一视同仁地扫关键词,包括正常的回复。这就是误杀的根源。
修复方向
#12309 给出的建议很清晰:sanitizeUserFacingText() 应该只做两件事——
- 清除内部标签(比如
<thinking>标签) - 合并重复的文本块
不应该在这个函数里做错误模式匹配。错误检测应该交给专门的错误处理路径,由 stopReason 等结构化信号门控。
这个思路的核心是"数据面和控制面分离"。Agent 输出的文本是数据面。Agent 的执行状态(成功、失败、超时、溢出)是控制面。不要用数据面的内容去推断控制面的状态。
更广的教训:Agent 消息后处理的陷阱
这个 bug 不只是 OpenClaw 的问题。任何 Agent 系统在把模型输出分发到多个渠道时,都可能遇到类似的后处理陷阱。
陷阱一:渠道不一致。WebChat 看到的和 Telegram 看到的不一样。如果你的 Agent 接了多个渠道,一定要验证同一条消息在所有渠道上的表现。
陷阱二:后处理改变语义。后处理逻辑(格式转换、内容过滤、敏感词替换)可能改变消息的含义。一个 Markdown 表格在 Telegram 上变成乱码,一段正常回复被 sanitize 函数替换成错误提示——这些都是后处理引入的问题。
陷阱三:过度保护。出于安全考虑,把各种可能暴露内部状态的关键词都加进过滤列表。结果正常内容被大面积误杀。安全和可用性之间需要平衡。
临时绕过方案
如果你现在被这个 bug 困扰,有几个临时办法。
改用 WebChat 作为主渠道。WebChat 不走 sanitizeUserFacingText(),不会被误杀。但这违背了 OpenClaw"用你习惯的聊天工具"的初衷。
避免在对话中直接提到这些关键词。比如用"上下文超出限制"代替"context overflow",用"请求超时"代替"timed out"。这是一种荒谬的妥协,但现阶段管用。
等待修复。#12309 的修复方案很明确,大概率会在下个版本解决。已有先例——2026.2.6 已经把 isBillingErrorMessage() 从 sanitizeUserFacingText() 里移除了(参考 PR #12226),context overflow 的检测大概率也会被移除。
参考链接
- OpenClaw Issue #12309(sanitize 误杀):
https://github.com/openclaw/openclaw/issues/12309 - 相关修复先例 PR #12226:移除了
isBillingErrorMessage()的文本匹配