From 8acbf209cd4aa0ccb145cf72afc951a1f5b9b2c9 Mon Sep 17 00:00:00 2001 From: xingzhou Date: Tue, 14 Jul 2026 17:41:55 +0800 Subject: [PATCH] fix(gateway): prevent broken emoji in WebSocket log IDs (#105001) * fix(gateway): preserve Unicode in WebSocket log IDs * test(gateway): cover UTF-16-safe log IDs --------- Co-authored-by: Peter Steinberger --- src/gateway/ws-log.test.ts | 23 +++++++++++++++++++++++ src/gateway/ws-log.ts | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/gateway/ws-log.test.ts b/src/gateway/ws-log.test.ts index cf45b219333f..775e9b7636d7 100644 --- a/src/gateway/ws-log.test.ts +++ b/src/gateway/ws-log.test.ts @@ -5,6 +5,29 @@ import { describe, expect, test } from "vitest"; import { formatForLog, summarizeAgentEventForWsLog } from "./ws-log.js"; describe("gateway ws log helpers", () => { + test.each([ + { + name: "run ID prefix boundary", + payload: { runId: `${"a".repeat(11)}🚀${"b".repeat(20)}` }, + field: "run", + expected: `${"a".repeat(11)}…bbbb`, + }, + { + name: "tool-call ID suffix boundary", + payload: { + stream: "tool", + data: { toolCallId: `${"a".repeat(25)}🚀bbb` }, + }, + field: "call", + expected: `${"a".repeat(12)}…bbb`, + }, + ])("summarizeAgentEventForWsLog keeps the $name UTF-16 safe", ({ payload, field, expected }) => { + const value = summarizeAgentEventForWsLog(payload)[field]; + + expect(value).toBe(expected); + expect(value).not.toMatch(/[\uD800-\uDFFF]/); + }); + test.each([ { name: "formats Error instances", diff --git a/src/gateway/ws-log.ts b/src/gateway/ws-log.ts index 6561229295c0..f8a572e14478 100644 --- a/src/gateway/ws-log.ts +++ b/src/gateway/ws-log.ts @@ -1,7 +1,7 @@ // Gateway WebSocket log formatting. // Redacts and compacts request/response/event metadata for console diagnostics. import { readStringValue } from "@openclaw/normalization-core/string-coerce"; -import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; +import { sliceUtf16Safe, truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import chalk from "chalk"; import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload"; import { isVerbose } from "../globals.js"; @@ -104,12 +104,12 @@ export function shouldLogWs(): boolean { function shortId(value: string): string { const s = value.trim(); if (UUID_RE.test(s)) { - return `${s.slice(0, 8)}…${s.slice(-4)}`; + return `${sliceUtf16Safe(s, 0, 8)}…${sliceUtf16Safe(s, -4)}`; } if (s.length <= 24) { return s; } - return `${s.slice(0, 12)}…${s.slice(-4)}`; + return `${sliceUtf16Safe(s, 0, 12)}…${sliceUtf16Safe(s, -4)}`; } /** Formats and redacts arbitrary values before they are written to gateway logs. */