Skip to content

fix: silent token prefix detection leaks partial tokens to webchat#32112

Closed
x4v13r1120 wants to merge 1 commit intoopenclaw:mainfrom
x4v13r1120:fix/silent-token-prefix-leak
Closed

fix: silent token prefix detection leaks partial tokens to webchat#32112
x4v13r1120 wants to merge 1 commit intoopenclaw:mainfrom
x4v13r1120:fix/silent-token-prefix-leak

Conversation

@x4v13r1120
Copy link

Problem

During streaming, \isSilentReplyPrefixText\ is used to detect partial emission of silent tokens (\NO_REPLY, \HEARTBEAT_OK) before the full token is complete. However, the function required an underscore character to be present in the partial text:

\\ s
if (!normalized.includes('_')) return false;
\\

This means streaming chunks like \NO\ (emitted before _REPLY\ arrives) were not recognized as prefixes and leaked through to users as visible messages in webchat.

Root Cause

When a model streams \NO_REPLY, the text may arrive in chunks: \NO\ → _REPLY. The prefix detector should recognize \NO\ as a potential prefix of \NO_REPLY, but the underscore gate rejected it. Same issue affects \HEARTBEAT_OK\ — \HEART\ would also leak.

Fix

  • Remove the underscore requirement
  • Add minimum length of 2 to prevent single-character false positives (\N, \H)
  • Require strict prefix matching (\ rimmed.length < token.length) so the full token falls through to \isSilentReplyText\ instead
  • Apply the uppercase-only regex to the original trimmed text (not after \ oUpperCase()) to prevent natural-language words like \No\ from matching

Tests

Updated \ okens.test.ts:

  • \NO\ → now correctly detected as prefix of \NO_REPLY\ ✅
  • \HEART\ → now correctly detected as prefix of \HEARTBEAT_OK\ ✅
  • \NO_REPLY\ (full token) → rejected as prefix (not a strict prefix) ✅
  • \No\ (mixed case) → still rejected ✅
  • \N\ (single char) → still rejected ✅
  • All 18 tests passing

isSilentReplyPrefixText required an underscore in the partial text,
so streaming chunks like 'NO' (before '_REPLY' arrives) were not
recognized as prefixes and leaked through to users as visible messages.

Fix: remove the underscore requirement, add a minimum length of 2 to
prevent single-character false positives, and require strict prefix
matching (shorter than the full token). The uppercase-only check is
applied to the original text (not after toUpperCase normalization) to
prevent natural-language words like 'No' from matching.

Fixes streaming NO_REPLY / HEARTBEAT_OK token leak on webchat.
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR fixes a bug where partial streaming chunks of silent reply tokens (e.g., NO before _REPLY arrives) were not being caught by isSilentReplyPrefixText and leaked as visible messages to webchat users. The fix removes the underscore gate, applies the uppercase-only regex to the un-uppercased text to reject natural-language mixed-case input, adds a minimum length of 2, and enforces strict prefix matching (so the full token falls through to isSilentReplyText).

Key changes:

  • tokens.ts: Refactored isSilentReplyPrefixText — dropped toUpperCase() before the regex check, removed the includes('_') guard, enforced trimmed.length < 2 early exit, and tightened the return condition to trimmed.length < token.length && token.toUpperCase().startsWith(trimmed).
  • tokens.test.ts: Updated test suite to assert the new behavior — "NO" and "HEART" are now recognized as valid prefixes, "NO_REPLY" and "HEARTBEAT_OK" (full tokens) are rejected as prefixes, and single-char / mixed-case inputs remain rejected.

Confidence Score: 5/5

  • Safe to merge — the fix is logically sound, well-tested, and scoped entirely to the prefix detection utility.
  • The change is minimal and targeted. The regex is now correctly applied before uppercasing, which is the key insight that prevents natural-language mixed-case words from matching while still catching all-caps streaming chunks. The strict prefix condition (trimmed.length < token.length) cleanly hands off the full token to isSilentReplyText. All 18 tests pass and the new cases added directly mirror the scenarios described in the bug report. No side effects on isSilentReplyText or stripSilentToken.
  • No files require special attention.

Last reviewed commit: 05d949f

@Takhoffman
Copy link
Contributor

Closing as superseded by merged #32183 plus already-landed #32116 for the webchat NO_REPLY leak family. This branch overlaps via shared token-prefix semantics and is broader than required for the canonical fix.

@Takhoffman Takhoffman closed this Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants