Skip to content

Commit 9b1c0da

Browse files
committed
fix(e2e): bound codex live assertion reads
1 parent cfaae77 commit 9b1c0da

4 files changed

Lines changed: 116 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
4242
- Release/CI/E2E: main CI guard drift, PR merge diff scoping, live Docker credential staging, base-image qualification, installer Docker classification, Playwright dependency install recovery, API-key auth for Codex live Docker lanes, Parallels option terminators, and JSON-mode progress handling are tighter so release proof fails cleaner. (#90532, #90287, #90058) Thanks @RomneyDa, @hxy91819, and @mrunalp.
4343
- Release/CI/E2E: Docker E2E and live Docker harness runs now apply default memory, CPU, and process ceilings while preserving explicit per-lane overrides.
4444
- Release/CI/E2E: plugin lifecycle matrix resource sampling now fails phases that exceed RSS, wall-clock, or CPU ceilings instead of only logging the measurements.
45+
- Release/CI/E2E: Codex npm plugin live assertions now cap transcript discovery and diagnostic log reads so failure proof stays bounded.
4546
- Tests/state isolation: provider, media, auth, cron, task, session, sandbox, Gateway, and Codex timeout fixtures now scope more home/state/env data per test, reducing cross-test leakage and making release validation failures less noisy. (#90027, #89974)
4647

4748
## 2026.6.2

scripts/e2e/codex-npm-plugin-live-docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ fi
308308
if ! grep -Fq 'Requested agent harness "codex" is not registered' /tmp/openclaw-codex-agent-after-uninstall.err &&
309309
! grep -Fq 'Unknown model: codex/' /tmp/openclaw-codex-agent-after-uninstall.err; then
310310
echo "Unexpected post-uninstall agent error:" >&2
311-
cat /tmp/openclaw-codex-agent-after-uninstall.err >&2 || true
311+
tail -n 120 /tmp/openclaw-codex-agent-after-uninstall.err >&2 || true
312312
exit 1
313313
fi
314314

scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,64 @@ import {
1717
const command = process.argv[2];
1818
const allowBetaCompatDiagnostics =
1919
process.env.OPENCLAW_CODEX_NPM_PLUGIN_ALLOW_BETA_COMPAT_DIAGNOSTICS === "1";
20+
const MAX_TEXT_FILE_BYTES = readPositiveIntEnv(
21+
"OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TEXT_FILE_BYTES",
22+
1024 * 1024,
23+
);
24+
const MAX_ERROR_TAIL_BYTES = readPositiveIntEnv(
25+
"OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_ERROR_TAIL_BYTES",
26+
64 * 1024,
27+
);
28+
const MAX_TRANSCRIPT_FILES = readPositiveIntEnv(
29+
"OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_FILES",
30+
64,
31+
);
32+
const MAX_TRANSCRIPT_WALK_ENTRIES = readPositiveIntEnv(
33+
"OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES",
34+
4096,
35+
);
36+
const MAX_TRANSCRIPT_SCAN_BYTES = readPositiveIntEnv(
37+
"OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_SCAN_BYTES",
38+
2 * 1024 * 1024,
39+
);
40+
41+
function readPositiveIntEnv(name, fallback) {
42+
const text = String(process.env[name] ?? fallback).trim();
43+
if (!/^\d+$/u.test(text)) {
44+
throw new Error(`${name} must be a positive integer; got: ${text}`);
45+
}
46+
const value = Number(text);
47+
if (!Number.isSafeInteger(value) || value <= 0) {
48+
throw new Error(`${name} must be a positive integer; got: ${text}`);
49+
}
50+
return value;
51+
}
52+
53+
function readTextFileBounded(filePath, label, maxBytes = MAX_TEXT_FILE_BYTES) {
54+
const stat = fs.statSync(filePath);
55+
if (stat.size > maxBytes) {
56+
throw new Error(`${label} exceeded ${maxBytes} bytes: ${filePath}`);
57+
}
58+
return fs.readFileSync(filePath, "utf8");
59+
}
60+
61+
function readTextFileTail(filePath, label, maxBytes = MAX_ERROR_TAIL_BYTES) {
62+
if (!fs.existsSync(filePath)) {
63+
return "";
64+
}
65+
const stat = fs.statSync(filePath);
66+
if (stat.size <= maxBytes) {
67+
return fs.readFileSync(filePath, "utf8");
68+
}
69+
const fd = fs.openSync(filePath, "r");
70+
try {
71+
const buffer = Buffer.alloc(maxBytes);
72+
fs.readSync(fd, buffer, 0, maxBytes, stat.size - maxBytes);
73+
return `[${label} truncated to last ${maxBytes} bytes]\n${buffer.toString("utf8")}`;
74+
} finally {
75+
fs.closeSync(fd);
76+
}
77+
}
2078

2179
function configure() {
2280
const modelRef = process.argv[3] || "codex/gpt-5.4";
@@ -255,7 +313,7 @@ function printCodexBin() {
255313

256314
function assertPreflight() {
257315
const marker = process.argv[3];
258-
const output = fs.readFileSync("/tmp/openclaw-codex-preflight.log", "utf8");
316+
const output = readTextFileBounded("/tmp/openclaw-codex-preflight.log", "Codex preflight log");
259317
if (!output.includes(marker)) {
260318
throw new Error(`Codex CLI preflight did not contain ${marker}:\n${output}`);
261319
}
@@ -267,10 +325,17 @@ function listFilesRecursive(root) {
267325
}
268326
const files = [];
269327
const stack = [root];
328+
let visited = 0;
270329
while (stack.length > 0) {
271330
const current = stack.pop();
272331
const entries = fs.readdirSync(current, { withFileTypes: true });
273332
for (const entry of entries) {
333+
visited += 1;
334+
if (visited > MAX_TRANSCRIPT_WALK_ENTRIES) {
335+
throw new Error(
336+
`native Codex session transcript walk exceeded ${MAX_TRANSCRIPT_WALK_ENTRIES} entries under ${root}`,
337+
);
338+
}
274339
const fullPath = path.join(current, entry.name);
275340
if (entry.isDirectory()) {
276341
stack.push(fullPath);
@@ -284,21 +349,29 @@ function listFilesRecursive(root) {
284349

285350
function assertNativeCodexSessionEvidence(params) {
286351
const roots = params.roots.filter((root) => fs.existsSync(root));
287-
const files = roots.flatMap((root) =>
288-
listFilesRecursive(root).filter((filePath) => filePath.endsWith(".jsonl")),
289-
);
352+
const files = roots
353+
.flatMap((root) => listFilesRecursive(root).filter((filePath) => filePath.endsWith(".jsonl")))
354+
.map((filePath) => ({ filePath, stat: fs.statSync(filePath) }))
355+
.toSorted((left, right) => right.stat.mtimeMs - left.stat.mtimeMs)
356+
.slice(0, MAX_TRANSCRIPT_FILES);
290357
if (files.length === 0) {
291358
throw new Error(
292359
`missing native Codex session transcript files; checked ${params.roots.join(", ")}`,
293360
);
294361
}
295-
const matchingFile = files.find((filePath) => {
296-
const content = fs.readFileSync(filePath, "utf8");
362+
let scannedBytes = 0;
363+
const matchingFile = files.find(({ filePath, stat }) => {
364+
const readableBytes = Math.min(stat.size, MAX_TEXT_FILE_BYTES);
365+
if (scannedBytes + readableBytes > MAX_TRANSCRIPT_SCAN_BYTES) {
366+
return false;
367+
}
368+
scannedBytes += readableBytes;
369+
const content = readTextFileTail(filePath, "native Codex session transcript", readableBytes);
297370
return content.includes(params.marker) || content.includes(params.threadId);
298-
});
371+
})?.filePath;
299372
if (!matchingFile) {
300373
throw new Error(
301-
`native Codex session transcripts did not contain ${params.marker} or ${params.threadId}; checked ${files.join(", ")}`,
374+
`native Codex session transcripts did not contain ${params.marker} or ${params.threadId}; scanned ${scannedBytes} bytes across ${files.length} newest files: ${files.map((entry) => entry.filePath).join(", ")}`,
302375
);
303376
}
304377
assertPathInside(params.codexHome, matchingFile, "native Codex session transcript");
@@ -308,10 +381,8 @@ function assertAgentTurn() {
308381
const marker = process.argv[3];
309382
const sessionId = process.argv[4];
310383
const modelRef = process.argv[5];
311-
const stdout = fs.readFileSync("/tmp/openclaw-codex-agent.json", "utf8");
312-
const stderr = fs.existsSync("/tmp/openclaw-codex-agent.err")
313-
? fs.readFileSync("/tmp/openclaw-codex-agent.err", "utf8")
314-
: "";
384+
const stdout = readTextFileBounded("/tmp/openclaw-codex-agent.json", "OpenClaw agent JSON");
385+
const stderr = readTextFileTail("/tmp/openclaw-codex-agent.err", "OpenClaw agent stderr");
315386
const response = JSON.parse(stdout);
316387
const text = extractAgentReplyTexts(JSON.stringify(response)).join("\n");
317388
if (!text.includes(marker)) {
@@ -405,10 +476,16 @@ function assertAgentError() {
405476
);
406477
}
407478
const stdout = fs.existsSync("/tmp/openclaw-codex-agent-after-uninstall.json")
408-
? fs.readFileSync("/tmp/openclaw-codex-agent-after-uninstall.json", "utf8")
479+
? readTextFileTail(
480+
"/tmp/openclaw-codex-agent-after-uninstall.json",
481+
"post-uninstall agent stdout",
482+
)
409483
: "";
410484
const stderr = fs.existsSync("/tmp/openclaw-codex-agent-after-uninstall.err")
411-
? fs.readFileSync("/tmp/openclaw-codex-agent-after-uninstall.err", "utf8")
485+
? readTextFileTail(
486+
"/tmp/openclaw-codex-agent-after-uninstall.err",
487+
"post-uninstall agent stderr",
488+
)
412489
: "";
413490
const combined = `${stdout}\n${stderr}`;
414491
if (

test/scripts/docker-build-helper.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const KITCHEN_SINK_RPC_DOCKER_E2E_PATH = "scripts/e2e/kitchen-sink-rpc-docker.sh
3737
const CODEX_ON_DEMAND_DOCKER_E2E_PATH = "scripts/e2e/codex-on-demand-docker.sh";
3838
const CODEX_MEDIA_PATH_SCENARIO_PATH = "scripts/e2e/lib/codex-media-path/scenario.sh";
3939
const CODEX_NPM_PLUGIN_LIVE_DOCKER_E2E_PATH = "scripts/e2e/codex-npm-plugin-live-docker.sh";
40+
const CODEX_NPM_PLUGIN_LIVE_ASSERTIONS_PATH =
41+
"scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs";
4042
const LIVE_PLUGIN_TOOL_DOCKER_E2E_PATH = "scripts/e2e/live-plugin-tool-docker.sh";
4143
const NPM_ONBOARD_CHANNEL_AGENT_DOCKER_E2E_PATH = "scripts/e2e/npm-onboard-channel-agent-docker.sh";
4244
const SKILL_INSTALL_DOCKER_E2E_PATH = "scripts/e2e/skill-install-docker.sh";
@@ -1822,6 +1824,27 @@ test -f "$TMPDIR/docker-cmd-seen"
18221824
expect(runner).not.toContain('rm -f "$run_log"\n exit 1');
18231825
});
18241826

1827+
it("bounds Codex npm plugin live assertion file and transcript reads", () => {
1828+
const assertions = readFileSync(CODEX_NPM_PLUGIN_LIVE_ASSERTIONS_PATH, "utf8");
1829+
const runner = readFileSync(CODEX_NPM_PLUGIN_LIVE_DOCKER_E2E_PATH, "utf8");
1830+
1831+
expect(assertions).toContain("OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TEXT_FILE_BYTES");
1832+
expect(assertions).toContain("OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_ERROR_TAIL_BYTES");
1833+
expect(assertions).toContain("OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_FILES");
1834+
expect(assertions).toContain("OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES");
1835+
expect(assertions).toContain("OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_SCAN_BYTES");
1836+
expect(assertions).toContain("readTextFileBounded");
1837+
expect(assertions).toContain("readTextFileTail");
1838+
expect(assertions).toContain(
1839+
".toSorted((left, right) => right.stat.mtimeMs - left.stat.mtimeMs)",
1840+
);
1841+
expect(assertions).toContain(".slice(0, MAX_TRANSCRIPT_FILES)");
1842+
expect(assertions).toContain("scannedBytes + readableBytes > MAX_TRANSCRIPT_SCAN_BYTES");
1843+
expect(assertions).not.toContain('const content = fs.readFileSync(filePath, "utf8")');
1844+
expect(runner).toContain("tail -n 120 /tmp/openclaw-codex-agent-after-uninstall.err");
1845+
expect(runner).not.toContain("cat /tmp/openclaw-codex-agent-after-uninstall.err");
1846+
});
1847+
18251848
it("cleans package-backed onboarding and plugin Docker artifacts on every exit path", () => {
18261849
for (const path of [
18271850
CODEX_ON_DEMAND_DOCKER_E2E_PATH,

0 commit comments

Comments
 (0)