Skip to content

Commit f650d64

Browse files
lsr911claudesteipete
authored
fix(codex): use truncateUtf16Safe in trajectory and client parse log truncation (#102576)
* fix(codex): use truncateUtf16Safe in trajectory and client parse log truncation Replace naive .slice(0, N) with truncateUtf16Safe() in: - trajectory.ts: payload redaction display (20k char limit) - client.ts: parse error log truncation Co-Authored-By: Claude <[email protected]> * test(codex): cover UTF-16 truncation boundaries --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent c86fe21 commit f650d64

4 files changed

Lines changed: 33 additions & 2 deletions

File tree

extensions/codex/src/app-server/client.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ describe("CodexAppServerClient", () => {
114114
).toBe("fatal OPENAI_API_KEY=<redacted> ANTHROPIC_API_KEY='<redacted>' OTHER=value");
115115
});
116116

117+
it("keeps malformed-message previews UTF-16 safe at the log boundary", () => {
118+
const prefix = "x".repeat(499);
119+
120+
expect(testing.redactCodexAppServerLinePreview(`${prefix}😀`)).toBe(`${prefix}...`);
121+
});
122+
117123
it("recovers app-server messages split by raw newlines inside JSON strings", async () => {
118124
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
119125
const harness = createClientHarness();

extensions/codex/src/app-server/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
import { createInterface, type Interface as ReadlineInterface } from "node:readline";
66
import { embeddedAgentLog, OPENCLAW_VERSION } from "openclaw/plugin-sdk/agent-harness-runtime";
7+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
78
import { resolveCodexAppServerRuntimeOptions, type CodexAppServerStartOptions } from "./config.js";
89
import {
910
type CodexAppServerRequestMethod,
@@ -739,7 +740,7 @@ function redactCodexAppServerLinePreview(value: string): string {
739740
"$1$2$3<redacted>$4",
740741
);
741742
return redacted.length > CODEX_APP_SERVER_PARSE_LOG_MAX
742-
? `${redacted.slice(0, CODEX_APP_SERVER_PARSE_LOG_MAX)}...`
743+
? `${truncateUtf16Safe(redacted, CODEX_APP_SERVER_PARSE_LOG_MAX)}...`
743744
: redacted;
744745
}
745746

extensions/codex/src/app-server/trajectory.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ describe("Codex trajectory recorder", () => {
8787
expect(fs.existsSync(path.join(tmpDir, "session.trajectory-path.json"))).toBe(true);
8888
});
8989

90+
it("keeps recorded string values UTF-16 safe at the trajectory boundary", async () => {
91+
const tmpDir = makeTempDir();
92+
const recorder = createCodexTrajectoryRecorder({
93+
cwd: tmpDir,
94+
attempt: {
95+
sessionFile: path.join(tmpDir, "session.jsonl"),
96+
sessionId: "session-1",
97+
model: { api: "responses" },
98+
} as never,
99+
env: {},
100+
});
101+
const prefix = "x".repeat(19_999);
102+
103+
const trajectoryRecorder = expectTrajectoryRecorder(recorder);
104+
trajectoryRecorder.recordEvent("model.output", { text: `${prefix}😀` });
105+
await trajectoryRecorder.flush();
106+
107+
const parsed = JSON.parse(
108+
fs.readFileSync(path.join(tmpDir, "session.trajectory.jsonl"), "utf8"),
109+
);
110+
expect(parsed.data.text).toBe(`${prefix}…`);
111+
});
112+
90113
it("records canonical OpenAI Codex app-server turns with Codex local attribution", async () => {
91114
const tmpDir = makeTempDir();
92115
const sessionFile = path.join(tmpDir, "session.jsonl");

extensions/codex/src/app-server/trajectory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
appendRegularFile,
1515
resolveRegularFileAppendFlags,
1616
} from "openclaw/plugin-sdk/security-runtime";
17+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
1718
import { resolveCodexLocalRuntimeAttribution } from "./local-runtime-attribution.js";
1819
import { flattenCodexDynamicToolFunctions, type CodexDynamicToolSpec } from "./protocol.js";
1920

@@ -377,7 +378,7 @@ function sanitizeValue(value: unknown, depth = 0, key = ""): unknown {
377378
return "<redacted payload>";
378379
}
379380
const redacted = redactSensitiveString(value);
380-
return redacted.length > 20_000 ? `${redacted.slice(0, 20_000)}…` : redacted;
381+
return redacted.length > 20_000 ? `${truncateUtf16Safe(redacted, 20_000)}…` : redacted;
381382
}
382383
if (depth >= 6) {
383384
return "<truncated>";

0 commit comments

Comments
 (0)