Skip to content

Strip NO_REPLY token from mixed-content messages #30955

@albertovasquez

Description

@albertovasquez

Problem

When an agent sends a message like 😄 NO_REPLY, the isSilentReplyText check fails (it requires exact match with ^\s*NO_REPLY\s*$), so the entire message including the NO_REPLY token leaks through to the user as visible text.

Expected Behavior

  • NO_REPLY alone → suppress (current, works ✅)
  • 😄 NO_REPLY → send 😄 (strip the token, deliver the content)
  • 😄 → send 😄 (no change needed)

Proposed Fix

In src/auto-reply/reply/normalize-reply.ts, after the isSilentReplyText exact-match check (~line 40), add a strip-and-keep fallback:

import { escapeRegExp } from "../../utils.js";

// Strip NO_REPLY token from mixed-content messages (e.g. "😄 NO_REPLY")
if (text && !isSilentReplyText(text, silentToken) && text.includes(silentToken)) {
  text = text.replace(new RegExp(`\\s*${escapeRegExp(silentToken)}\\s*`, "g"), "").trim();
  if (!text && !hasMedia && !hasChannelData) {
    opts.onSkip?.("silent");
    return null;
  }
}

Test cases for tokens.test.ts:

it("strips NO_REPLY from mixed emoji message", () => {
  // This would be tested via normalizeReplyPayload, not isSilentReplyText
  expect(isSilentReplyText("😄 NO_REPLY")).toBe(false); // correctly not silent
});

Test cases for normalize-reply (new or existing test file):

it("strips NO_REPLY from mixed content and delivers remaining text", () => {
  const result = normalizeReplyPayload({ text: "😄 NO_REPLY" });
  expect(result).not.toBeNull();
  expect(result!.text).toBe("😄");
});

it("strips NO_REPLY when it appears before content", () => {
  const result = normalizeReplyPayload({ text: "NO_REPLY 👍" });
  expect(result).not.toBeNull();
  expect(result!.text).toBe("👍");
});

it("still suppresses pure NO_REPLY", () => {
  const result = normalizeReplyPayload({ text: "NO_REPLY" });
  expect(result).toBeNull();
});

Context

Discovered in a Quiubo group chat when an agent sent 😄 NO_REPLY — the raw text including the token was delivered to all participants.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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