Skip to content

Commit 696c624

Browse files
jailbirtclaudeopenclaw-clownfish[bot]
authored
fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684) (#95084)
* 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]> * fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684) --------- Co-authored-by: Claude Code <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
1 parent b3b8b28 commit 696c624

2 files changed

Lines changed: 24 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+
// Google Chat's plain-text pass does not remove assistant scaffolding.
199+
// Run the canonical delivery sanitizer first so internal tool traces are
200+
// dropped before channel formatting.
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,3 +843,21 @@ 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+
"Visible answer.\n" + "⚠️ 🛠️ `run openclaw definitely-not-a-real-subcommand (agent)` failed";
853+
const out = sanitizeText({ text });
854+
expect(out).toBe("Visible answer.");
855+
expect(out).not.toContain("failed");
856+
expect(out).not.toContain("🛠️");
857+
});
858+
859+
it("preserves ordinary assistant prose untouched", () => {
860+
const text = "El pipeline tiene 3 deals abiertos por USD 12.000.";
861+
expect(sanitizeText({ text })).toBe(text);
862+
});
863+
});

0 commit comments

Comments
 (0)