Skip to content

fix: suppress partial NO_REPLY tokens at lifecycle boundary#23761

Open
kami-saia wants to merge 2 commits intoopenclaw:mainfrom
kami-saia:fix/noreply-partial-token-leak
Open

fix: suppress partial NO_REPLY tokens at lifecycle boundary#23761
kami-saia wants to merge 2 commits intoopenclaw:mainfrom
kami-saia:fix/noreply-partial-token-leak

Conversation

@kami-saia
Copy link

@kami-saia kami-saia commented Feb 22, 2026

Problem

When the model streams NO_REPLY as multiple tokens (e.g. "NO" then "_REPLY"), the lifecycle:end event can fire while the buffer only contains the partial prefix. isSilentReplyText() returns false for "NO", causing it to leak to connected clients as a real assistant message.

This is particularly visible on node clients (VS Code extensions, mobile nodes) that display every chat-final message as a bubble — users see random "NO" or "NO_" messages appear.

Fix

Add a partial-prefix check in emitChatFinal: if the buffered text is a strict prefix of SILENT_REPLY_TOKEN, is shorter than the full token, and contains only [A-Z_] characters, treat it as a silent reply and suppress it.

The check is deliberately conservative:

  • Minimum 2 characters (avoids matching single letters)
  • Must be strictly shorter than the full token (full match already handled by isSilentReplyText)
  • Only uppercase letters and underscores (won't match real messages)

Testing

Tested in production with a VS Code node client (Pawr) over multiple days. No more leaked partial tokens.

Fixes #3340

Greptile Summary

Adds logic to suppress partial NO_REPLY tokens that leak when the model streams the token across multiple chunks and lifecycle:end fires before the complete token arrives. The fix checks if buffered text is a strict prefix of SILENT_REPLY_TOKEN before emitting chat-final events.

  • Prevents partial tokens like "NO" or "NO_" from appearing as assistant messages in client UIs
  • Conservative approach with minimum 2-char length and character class validation
  • Complements existing isSilentReplyText() which handles complete tokens

Confidence Score: 4/5

  • Safe to merge - addresses real production bug with conservative logic
  • The fix correctly addresses the partial token leak issue with appropriate boundary checks. Logic has been tested in production. Minor style improvement suggested on regex flag but doesn't affect correctness
  • No files require special attention

Last reviewed commit: abe3c10

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!


🤖 AI-assisted (Claude Opus/Sonnet via OpenClaw agent). Fully tested in production.

When the model streams NO_REPLY as multiple tokens (e.g. 'NO' then
'_REPLY'), the lifecycle:end event can fire while the buffer only
contains the partial prefix. isSilentReplyText() returns false for 'NO',
causing it to leak to connected clients as a real assistant message.

This is particularly visible on node clients (VS Code extensions, mobile
nodes) that display every chat-final message as a bubble.

Fix: add a partial-prefix check — if the buffered text is a strict prefix
of SILENT_REPLY_TOKEN and contains only [A-Z_] characters, treat it as
a silent reply and suppress it.

Fixes openclaw#3340
@openclaw-barnacle openclaw-barnacle bot added gateway Gateway runtime size: XS labels Feb 22, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

text.length >= 2 &&
text.length < SILENT_REPLY_TOKEN.length &&
SILENT_REPLY_TOKEN.startsWith(text.toUpperCase()) &&
/^[A-Z_]+$/i.test(text);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the /i flag makes [A-Z_] match lowercase too - if intent is uppercase-only, remove /i flag

Suggested change
/^[A-Z_]+$/i.test(text);
/^[A-Z_]+$/.test(text);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server-chat.ts
Line: 345

Comment:
the `/i` flag makes `[A-Z_]` match lowercase too - if intent is uppercase-only, remove `/i` flag

```suggestion
      /^[A-Z_]+$/.test(text);
```

How can I resolve this? If you propose a fix, please make it concise.

@kami-saia
Copy link
Author

Good catch — removed the /i flag. Since we call .toUpperCase() on text before the regex test, the flag was redundant and misleading.

@openclaw-barnacle
Copy link

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Feb 28, 2026
@kami-saia
Copy link
Author

Bumping.

@openclaw-barnacle openclaw-barnacle bot removed the stale Marked as stale due to inactivity label Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: NO_REPLY leaks into webchat responses (silent token not filtered)

1 participant