openclaw - ✅(Solved) Fix NO_REPLY detection fails when model prepends reasoning/think blocks [1 pull requests, 1 participants]

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…
GitHub stats
openclaw/openclaw#66701Fetched 2026-04-15 06:24:49
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

When a model decides not to respond in a group chat, it should output exactly NO_REPLY so the channel plugin suppresses the message. However, some models (observed with Gemini and Haiku) prepend their reasoning before NO_REPLY:

think
Cav is talking about a follow-up conversation...
I will stay quiet here.NO_REPLY

The NO_REPLY detection expects the entire response to be exactly NO_REPLY (with optional leading/trailing whitespace). When reasoning text is prepended, the detection fails and the raw reasoning + NO_REPLY is posted to the chat as a visible message.

Root Cause

When a model decides not to respond in a group chat, it should output exactly NO_REPLY so the channel plugin suppresses the message. However, some models (observed with Gemini and Haiku) prepend their reasoning before NO_REPLY:

think
Cav is talking about a follow-up conversation...
I will stay quiet here.NO_REPLY

The NO_REPLY detection expects the entire response to be exactly NO_REPLY (with optional leading/trailing whitespace). When reasoning text is prepended, the detection fails and the raw reasoning + NO_REPLY is posted to the chat as a visible message.

Fix Action

Fixed

PR fix notes

PR #66755: fix(auto-reply): detect silent NO_REPLY wrapped in model reasoning

Description (problem / solution / changelog)

Summary

  • Fixes #66701

  • Some models (observed with Gemini and Haiku) prepend chain-of-thought before the silent token, e.g.:

    ``` think Cav is talking about a follow-up conversation... I will stay quiet here.NO_REPLY ```

  • The previous exact-match detection required the entire response to be `NO_REPLY`, so reasoning-wrapped outputs leaked the raw thought process into group chats.

  • Add a narrow second path in `isSilentReplyText`: if the message starts with a reasoning marker (`<think>...</think>` or a bare `think\n` preamble) and ends with the silent token, treat it as silent.

Why this is narrow

The #19537 behavior is preserved — substantive replies that happen to end with NO_REPLY are still NOT suppressed. Only messages that look like "reasoning block + silent token" trigger the new branch. Custom tokens (`HEARTBEAT_OK`, etc.) flow through the same logic.

Test plan

  • `pnpm vitest run src/auto-reply/tokens.test.ts` — 30/30 pass (23 existing + 7 new)
  • `pnpm check` (via pre-commit hook) — green
  • Regression tests for #19537 still pass ("Here is a helpful response.\n\nNO_REPLY" ⇒ not silent)

AI-assisted disclosure

  • AI-assisted (Claude)
  • Testing: fully tested — new unit tests cover each reasoning-wrapping shape plus negative cases; `pnpm check` green.
  • Prompt context: issue #66701 examples, plus reads of `src/auto-reply/tokens.ts` and `tokens.test.ts`.
  • I understand what the code does.

Changed files

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

Code Example

think
Cav is talking about a follow-up conversation...
I will stay quiet here.NO_REPLY
RAW_BUFFERClick to expand / collapse

Description

When a model decides not to respond in a group chat, it should output exactly NO_REPLY so the channel plugin suppresses the message. However, some models (observed with Gemini and Haiku) prepend their reasoning before NO_REPLY:

think
Cav is talking about a follow-up conversation...
I will stay quiet here.NO_REPLY

The NO_REPLY detection expects the entire response to be exactly NO_REPLY (with optional leading/trailing whitespace). When reasoning text is prepended, the detection fails and the raw reasoning + NO_REPLY is posted to the chat as a visible message.

Impact

Bot posts its internal chain-of-thought reasoning into group chats, which is embarrassing and confusing for users. This is especially common in rooms with requireMention: false where the bot evaluates every message.

Expected Behavior

NO_REPLY detection should:

  1. Strip any leading think/<think> blocks and whitespace
  2. Check if the remaining content is NO_REPLY (or if NO_REPLY appears as the final token)
  3. Suppress the entire message if NO_REPLY is detected anywhere in the output

Reproduction

  1. Configure a NC Talk (or any channel) room with requireMention: false
  2. Have users chat casually without mentioning the bot
  3. Observe the bot occasionally posting think\n...\nNO_REPLY as a visible message

Environment

  • OpenClaw: 2026.3.31+
  • Models observed: google/gemini-3.1-pro-preview, anthropic/claude-haiku-3-5
  • Channel: nextcloud-talk (likely affects all channels)

extent analysis

TL;DR

Update the NO_REPLY detection logic to strip leading think blocks and whitespace, and check for NO_REPLY anywhere in the response, not just as the entire response.

Guidance

  • Modify the NO_REPLY detection to use a regular expression that matches NO_REPLY as a whole word, ignoring any leading or trailing text, to correctly identify and suppress messages.
  • Consider adding a preprocessing step to remove any think blocks and whitespace from the model's response before applying the NO_REPLY detection.
  • Review the channel plugin's message suppression logic to ensure it correctly handles cases where NO_REPLY is detected, to prevent accidental posting of internal reasoning.
  • Test the updated detection logic with various model responses, including those from Gemini and Haiku, to ensure it correctly suppresses messages with prepended reasoning text.

Example

import re

def detect_no_reply(response):
    # Remove leading think blocks and whitespace
    response = re.sub(r'^think\s*', '', response, flags=re.IGNORECASE)
    response = response.strip()
    
    # Check for NO_REPLY anywhere in the response
    if re.search(r'\bNO_REPLY\b', response):
        return True
    return False

Notes

This solution assumes that the NO_REPLY detection logic is currently implemented using a simple string comparison, and that updating this logic will correctly fix the issue. However, the actual implementation details may vary depending on the specific codebase and requirements.

Recommendation

Apply workaround: Update the NO_REPLY detection logic to correctly handle prepended reasoning text, as this will provide a more robust solution and prevent accidental posting of internal reasoning.

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