Skip to content

Commit 9449e15

Browse files
committed
fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684)
Google Chat ran only sanitizeForPlainText() on outbound text, so internal assistant trace lines leaked to users — most visibly the `⚠️ 🛠️ <command> (agent) failed` banner emitted for benign non-zero shell exits (e.g. a grep with no match, exit 1). The turn succeeds but the user gets an alarming "failed" message. Apply sanitizeAssistantVisibleText() before sanitizeForPlainText() in the googlechat outbound sanitizeText hook, mirroring Discord and WhatsApp. The hook runs pre-chunking in the shared deliver path, covering all text sends. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1 parent efc36d7 commit 9449e15

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

extensions/googlechat/src/channel.adapters.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
2323
import type { OutboundMediaLoadOptions } from "openclaw/plugin-sdk/outbound-media";
2424
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
2525
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
26+
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
2627
import { shouldSuppressGoogleChatManualExecApprovalFollowupPayload } from "./approval-card-actions.js";
2728
import { formatGoogleChatAllowFromEntry } from "./channel-base.js";
2829
import {
@@ -194,7 +195,11 @@ export const googlechatOutboundAdapter = {
194195
chunker: chunkTextForOutbound,
195196
chunkerMode: "markdown" as const,
196197
textChunkLimit: 4000,
197-
sanitizeText: ({ text }: { text: string }) => sanitizeForPlainText(text),
198+
// Strip internal assistant trace lines (e.g. `⚠️ 🛠️ ... (agent) failed`
199+
// banners from benign non-zero shell exits) before the plain-text pass, so
200+
// they don't leak to users — mirrors Discord/WhatsApp outbound. See #90684.
201+
sanitizeText: ({ text }: { text: string }) =>
202+
sanitizeForPlainText(sanitizeAssistantVisibleText(text)),
198203
normalizePayload: ({ payload }: { payload: ReplyPayload }) =>
199204
shouldSuppressGoogleChatManualExecApprovalFollowupPayload(payload) ? null : payload,
200205
resolveTarget: ({ to }: { to?: string }) => {

extensions/googlechat/src/channel.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,3 +843,22 @@ describe("googlechatPlugin security", () => {
843843
);
844844
});
845845
});
846+
847+
describe("googlechatPlugin outbound sanitizeText", () => {
848+
const sanitizeText = googlechatOutboundAdapter.base.sanitizeText;
849+
850+
it("strips internal tool-trace failure banners from outbound text (#90684)", () => {
851+
const text =
852+
"Listo, Javi — encontré el cron.\n" +
853+
'⚠️ 🛠️ `search "Pipeline" in ~/.openclaw/workspace-* (agent)` failed';
854+
const out = sanitizeText({ text });
855+
expect(out).toBe("Listo, Javi — encontré el cron.");
856+
expect(out).not.toContain("failed");
857+
expect(out).not.toContain("🛠️");
858+
});
859+
860+
it("preserves ordinary assistant prose untouched", () => {
861+
const text = "El pipeline tiene 3 deals abiertos por USD 12.000.";
862+
expect(sanitizeText({ text })).toBe(text);
863+
});
864+
});

0 commit comments

Comments
 (0)