Skip to content

Commit ce0d511

Browse files
committed
fix(e2e): fail codex app server log errors
1 parent 826cdd8 commit ce0d511

2 files changed

Lines changed: 92 additions & 3 deletions

File tree

scripts/e2e/lib/codex-media-path/fake-codex-app-server.mjs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,36 @@ const requestLog =
77
let turnCount = 0;
88

99
function appendRequest(request) {
10-
fs.appendFileSync(requestLog, `${JSON.stringify(request)}\n`);
10+
try {
11+
fs.appendFileSync(requestLog, `${JSON.stringify(request)}\n`);
12+
return true;
13+
} catch (error) {
14+
const message = error instanceof Error ? error.message : String(error);
15+
process.stderr.write(`fake Codex app-server request log write failed: ${message}\n`);
16+
if (request?.id != null) {
17+
sendError(request.id, `fake Codex app-server request log write failed: ${message}`);
18+
}
19+
return false;
20+
}
1121
}
1222

1323
function send(id, result) {
1424
process.stdout.write(`${JSON.stringify({ id, result })}\n`);
1525
}
1626

27+
function sendError(id, message) {
28+
process.stdout.write(`${JSON.stringify({ error: { message }, id })}\n`);
29+
}
30+
1731
const rl = readline.createInterface({ input: process.stdin });
1832
rl.on("line", (line) => {
1933
if (!line.trim()) {
2034
return;
2135
}
2236
const request = JSON.parse(line);
23-
appendRequest(request);
37+
if (!appendRequest(request)) {
38+
return;
39+
}
2440
const { id, method, params } = request;
2541
if (method === "initialize") {
2642
send(id, {

test/scripts/codex-media-path-client.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { spawnSync } from "node:child_process";
1+
import { type ChildProcessWithoutNullStreams, spawn, spawnSync } from "node:child_process";
22
import { appendFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
33
import { tmpdir } from "node:os";
44
import path from "node:path";
@@ -7,6 +7,9 @@ import { createJsonlRequestTailer } from "../../scripts/e2e/lib/codex-media-path
77
import { readPositiveIntEnv } from "../../scripts/e2e/lib/codex-media-path/limits.mjs";
88

99
const tempRoots: string[] = [];
10+
const fakeAppServerPath = path.resolve(
11+
"scripts/e2e/lib/codex-media-path/fake-codex-app-server.mjs",
12+
);
1013
const writeConfigPath = path.resolve("scripts/e2e/lib/codex-media-path/write-config.mjs");
1114

1215
function makeTempRoot(): string {
@@ -42,6 +45,42 @@ function runWriteConfig(root: string, env: Record<string, string> = {}) {
4245
});
4346
}
4447

48+
async function readStdoutLine(child: ChildProcessWithoutNullStreams): Promise<string> {
49+
return await new Promise<string>((resolve, reject) => {
50+
let stdout = "";
51+
const timeout = setTimeout(() => {
52+
reject(new Error(`timed out waiting for fake app-server response: ${stdout}`));
53+
}, 3_000);
54+
child.stdout.setEncoding("utf8");
55+
child.stdout.on("data", (chunk: string) => {
56+
stdout += chunk;
57+
const line = stdout.split("\n").find((entry) => entry.trim());
58+
if (line) {
59+
clearTimeout(timeout);
60+
resolve(line);
61+
}
62+
});
63+
child.once("error", (error) => {
64+
clearTimeout(timeout);
65+
reject(error);
66+
});
67+
child.once("exit", (code, signal) => {
68+
clearTimeout(timeout);
69+
reject(new Error(`fake app-server exited before response: code=${code} signal=${signal}`));
70+
});
71+
});
72+
}
73+
74+
async function stopChild(child: ChildProcessWithoutNullStreams): Promise<void> {
75+
if (child.exitCode !== null || child.signalCode !== null) {
76+
return;
77+
}
78+
await new Promise<void>((resolve) => {
79+
child.once("close", () => resolve());
80+
child.kill("SIGTERM");
81+
});
82+
}
83+
4584
afterEach(() => {
4685
for (const root of tempRoots.splice(0)) {
4786
rmSync(root, { recursive: true, force: true });
@@ -87,6 +126,40 @@ describe("codex media path limits", () => {
87126
});
88127
});
89128

129+
describe("codex media path fake app-server", () => {
130+
it("returns a structured error when request logging fails", async () => {
131+
const requestLogDirectory = makeTempRoot();
132+
const child: ChildProcessWithoutNullStreams = spawn(process.execPath, [fakeAppServerPath], {
133+
env: {
134+
...process.env,
135+
OPENCLAW_CODEX_MEDIA_PATH_APP_SERVER_LOG: requestLogDirectory,
136+
},
137+
stdio: ["pipe", "pipe", "pipe"],
138+
});
139+
let stderr = "";
140+
child.stderr.setEncoding("utf8");
141+
child.stderr.on("data", (chunk: string) => {
142+
stderr += chunk;
143+
});
144+
145+
try {
146+
const responseLine = readStdoutLine(child);
147+
child.stdin.write(jsonl({ id: "request-1", method: "initialize" }));
148+
const response = JSON.parse(await responseLine);
149+
150+
expect(response).toMatchObject({
151+
error: {
152+
message: expect.stringContaining("fake Codex app-server request log write failed"),
153+
},
154+
id: "request-1",
155+
});
156+
expect(stderr).toContain("fake Codex app-server request log write failed");
157+
} finally {
158+
await stopChild(child);
159+
}
160+
});
161+
});
162+
90163
describe("codex media path JSONL tailer", () => {
91164
it("keeps parsed app-server requests and reads only appended lines", () => {
92165
const logPath = path.join(makeTempRoot(), "app-server.jsonl");

0 commit comments

Comments
 (0)