openclaw - ✅(Solved) Fix NO_REPLY silent reply suppression fails when model prefaces with reasoning or emits doubled token [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

Three layered issues in src/auto-reply/tokens.ts:

  1. isSilentReplyText requires the message to be exactly NO_REPLY. Per #19537, anything else is considered substantive and passes through. When Gemini (or any reasoning model) emits reasoning as plain text content followed by the silent token, the content disqualifies the message from suppression.

  2. stripSilentToken does not collapse doubled tokens or tolerate punctuation. The regex (?:^|\s+|\*+)${token}\s*$ matches only when the trailing token is preceded by whitespace, start-of-string, or asterisks. The observed output "...NO_REPLY.NO_REPLY" has a literal . between the two tokens, so neither token gets stripped and the tail is not recognized as silent.

  3. stripReasoningTagsFromText (in src/shared/text/reasoning-tags.ts) only matches XML-style tags (<think>, <thinking>, <thought>, <antthinking>, <final>). It cannot catch Markdown-style reasoning preambles like a bare think\n... heading at the start of the message.

Fix Action

Fixed

PR fix notes

PR #69472: auto-reply: suppress reasoning-prefaced silent replies

Description (problem / solution / changelog)

Summary

  • Suppress messages where the model's reply is a bare reasoning preamble (a word like think, thinking, thought, reasoning, or analysis on its own line) followed by the NO_REPLY sentinel, instead of delivering the chain-of-thought verbatim to users.
  • Collapse doubled trailing tokens such as NO_REPLY.NO_REPLY (observed with Gemini 3.x) inside the new check, while keeping the existing stripSilentToken contract unchanged.
  • Preserve the #19537 semantics: substantive replies that happen to end with NO_REPLY are still delivered.

Closes #69470.

Problem

When a reasoning-prone model emits its chain-of-thought as plain text content (not via structured thinking_* stream events or <thinking> XML tags) and concludes with the silent-reply sentinel, isSilentReplyPayloadText returns false and the whole message — reasoning plus NO_REPLY — is delivered to the messaging channel.

Observed Discord output:

think
The user's message is from "Aftermath" in the #general channel.
The channel topic is: "Topics of general interest around PDD. No advertisement or soliciting."
...
Therefore, I should output NO_REPLY.NO_REPLY

The proper XML tag stripper in src/shared/text/reasoning-tags.ts cannot catch this form — there are no tags — and stripSilentToken intentionally leaves interject.NO_REPLY-style glued suffixes alone (per existing test at src/auto-reply/tokens.test.ts:64).

Fix

All changes localized to src/auto-reply/tokens.ts:

  1. New isReasoningPrefacedSilentReply(text, token) returns true when the message ends with the silent token and the preceding content is a bare reasoning preamble.
  2. Included in isSilentReplyPayloadText, so the single channel-agnostic gate at src/auto-reply/reply/reply-directives.ts:34 catches the leak for every channel.
  3. Private stripTrailingSilentTokensTolerant helper with its own regex (getSilentReasoningTrailingRegex) iterates to collapse doubled trailing tokens separated by punctuation. Kept out of the public stripSilentToken path so no other caller behavior shifts.

Test plan

  • pnpm test src/auto-reply/tokens.test.ts — 38 tests pass (16 new).
  • pnpm test src/auto-reply — 1086 tests pass.
  • Covers: reasoning-prefaced messages, all supported heading words, trailing-colon headings, doubled NO_REPLY.NO_REPLY, preserves #19537 substantive replies, rejects inline "think the answer is X" natural language, handles whitespace-only and undefined inputs.

🤖 Generated with Claude Code

Changed files

  • src/auto-reply/tokens.test.ts (modified, +85/-0)
  • src/auto-reply/tokens.ts (modified, +88/-1)

Code Example

think
The user's message is from "Aftermath" in the #general channel.
The channel topic is: "Topics of general interest around PDD. No advertisement or soliciting."
Aftermath's message is a self-promotional advertisement offering assistance with automation projects, websites, mobile apps, and AI systems.

According to the summary of progress and key decisions:
"NO_REPLY to Spam/Ads: Although ads violate the channel rules, the bot lacks moderation capabilities and its primary directive is PDD support. Silence is the required action."

Therefore, I should output NO_REPLY.NO_REPLY
RAW_BUFFERClick to expand / collapse

Bug

When an agent is configured to reply with the NO_REPLY sentinel (for example during spam/ad handling in Discord), the agent's model can emit reasoning as plain text content instead of (or in addition to) structured thinking events. The resulting message is posted to the messaging channel verbatim.

Observed Discord output:

think
The user's message is from "Aftermath" in the #general channel.
The channel topic is: "Topics of general interest around PDD. No advertisement or soliciting."
Aftermath's message is a self-promotional advertisement offering assistance with automation projects, websites, mobile apps, and AI systems.

According to the summary of progress and key decisions:
"NO_REPLY to Spam/Ads: Although ads violate the channel rules, the bot lacks moderation capabilities and its primary directive is PDD support. Silence is the required action."

Therefore, I should output NO_REPLY.NO_REPLY

This should have been suppressed (the agent correctly decided not to reply), but the user sees the model's reasoning instead.

Root Cause

Three layered issues in src/auto-reply/tokens.ts:

  1. isSilentReplyText requires the message to be exactly NO_REPLY. Per #19537, anything else is considered substantive and passes through. When Gemini (or any reasoning model) emits reasoning as plain text content followed by the silent token, the content disqualifies the message from suppression.

  2. stripSilentToken does not collapse doubled tokens or tolerate punctuation. The regex (?:^|\s+|\*+)${token}\s*$ matches only when the trailing token is preceded by whitespace, start-of-string, or asterisks. The observed output "...NO_REPLY.NO_REPLY" has a literal . between the two tokens, so neither token gets stripped and the tail is not recognized as silent.

  3. stripReasoningTagsFromText (in src/shared/text/reasoning-tags.ts) only matches XML-style tags (<think>, <thinking>, <thought>, <antthinking>, <final>). It cannot catch Markdown-style reasoning preambles like a bare think\n... heading at the start of the message.

Why Now

The same silent-reply path has existed for a while. This became visible once the gateway stabilized around 2026-04-16 (google-vertex ADC auth, dist/plugin-sdk build, context-overflow thrash all resolved). Prior to that, the agent frequently failed before ever reaching the silent-reply check. Additionally, raising agents.defaults.contextTokens from 80000 to 500000 gave Gemini more headroom to emit verbose reasoning as content.

Proposed Fix

Narrow, channel-agnostic change in src/auto-reply/tokens.ts:

  1. Iterate stripSilentToken so repeated NO_REPLY occurrences with intervening punctuation collapse to empty.
  2. Add isReasoningPrefacedSilentReply(text, token) that returns true when the message ends with the silent token and the preceding content is a bare reasoning preamble (a word like think, thinking, thought, reasoning, or analysis on its own line). This preserves the #19537 semantics — substantive replies ending with NO_REPLY are still delivered — while suppressing reasoning-wrapped silent replies.
  3. Include the new check in isSilentReplyPayloadText so the single gate at src/auto-reply/reply/reply-directives.ts:34 catches it.

Scope

  • No change to stripReasoningTagsFromText (XML tag stripping stays as-is).
  • No channel-specific changes; the fix lives in the shared auto-reply layer so all channels benefit.
  • Existing behavior (#19537: "do not suppress substantive replies that happen to end with NO_REPLY") is preserved.

extent analysis

TL;DR

The issue can be fixed by modifying the stripSilentToken function to collapse repeated NO_REPLY occurrences and adding a new check to identify reasoning-prefaced silent replies.

Guidance

  • Update the stripSilentToken function in src/auto-reply/tokens.ts to handle repeated NO_REPLY occurrences with intervening punctuation.
  • Implement the proposed isReasoningPrefacedSilentReply function to identify messages ending with the silent token and preceded by a bare reasoning preamble.
  • Integrate the new check into isSilentReplyPayloadText to catch reasoning-wrapped silent replies.
  • Verify the fix by testing with sample messages containing reasoning preambles and silent tokens.

Example

// Example implementation of isReasoningPrefacedSilentReply
function isReasoningPrefacedSilentReply(text: string, token: string): boolean {
  const reasoningPrefixes = ['think', 'thinking', 'thought', 'reasoning', 'analysis'];
  const lines = text.split('\n');
  const lastLine = lines[lines.length - 1];
  return lastLine.trim() === token && reasoningPrefixes.some(prefix => lines[0].trim() === prefix);
}

Notes

The proposed fix only modifies the shared auto-reply layer, ensuring that all channels benefit from the change without introducing channel-specific modifications.

Recommendation

Apply the proposed workaround by implementing the updated stripSilentToken function and the new isReasoningPrefacedSilentReply check, as this preserves the existing behavior of not suppressing substantive replies that end with NO_REPLY.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix NO_REPLY silent reply suppression fails when model prefaces with reasoning or emits doubled token [1 pull requests]