Skip to content

Feishu (and non-Discord channels) should apply sanitizeAssistantVisibleText() on outbound text #90684

Description

@studentzhou-svg

Summary

The sanitizeAssistantVisibleText() pipeline already strips internal XML scaffolding (tool-call tags, thinking tags, model tokens, legacy bracket blocks, etc.) — but it is only applied to Discord today. Feishu and other non-Discord channels send raw text directly, causing <invoke name="exec">, <tool_call>, <function_call> and similar XML tags to leak into user-visible messages.

Reproduction

  1. Use a model that emits XML tool-call scaffolding as plain text (e.g. DeepSeek V4, GLM-5, Kimi through non-native providers)
  2. When the model's exec command fails (e.g. unmatched shell quotes), the stderr contains the original prompt's tool-call XML tags
  3. The assistant sees this error output and may include it in the reply
  4. On Discord this is stripped before delivery; on Feishu/Telegram/WhatsApp it lands in the user's chat

Technical Analysis

What exists today

assistant-visible-text-ye5B62pZ.js exports sanitizeAssistantVisibleText() — a comprehensive pipeline that strips:

  • Model special tokens (<|...|>)
  • Thinking/reasoning/antthinking tags
  • <final> tags
  • <tool_call>, <tool_result>, <function_call>, <function_calls>, <function_response>, <function>, <tool_calls> XML blocks
  • [TOOL_CALL] / [TOOL_RESULT] legacy bracket blocks
  • Minimax tool-call XML (<invoke> with minimax:tool_call namespace)
  • <relevant_memories> tags
  • Downgraded tool-call text ([Tool Call:...] blocks)

Where it's used

Only the Discord channel calls it:

// reply-delivery-DaHJ1mRV.js
function sanitizeDiscordFrontChannelText(text) {
  return collapseExcessBlankLines(
    stripDiscordInternalTraceLines(sanitizeAssistantVisibleText(text))
  ).trim();
}

Where it's missing

Feishu's sendMessageFeishu() in send-BB9NHzC6.js sends raw text directly:

let rawText = text ?? "";
if (mentions && mentions.length > 0) rawText = buildMentionedMessage(mentions, rawText);
// ⬇️ MISSING: rawText = sanitizeAssistantVisibleText(rawText);
const { content, msgType } = buildFeishuPostMessagePayload({
  messageText: convertMarkdownTables(rawText, tableMode)
});

The same issue applies to sendStructuredCardFeishu() and sendMarkdownCardFeishu().

Likely also affects Telegram, WhatsApp, Slack, and other channels that don't apply this pipeline.

Proposed Solution

Option A: Per-channel config flag (minimal)

Add a sanitizeVisibleText boolean config field to the Feishu channel schema (default true), and call sanitizeAssistantVisibleText() before sending.

Option B: Global default (ideal)

Move the sanitization to the shared outbound pipeline (channel-outbound-DuRkxH3q.js or reply-payload-swpu2tGD.js) so all channels benefit automatically. Make it opt-out per channel via config.

Option C: Channel-level config

Add channels.feishu.sanitizeVisibleText and channels.*.sanitizeVisibleText as a standard channel field.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions