Skip to content

Commit 7e1bfc5

Browse files
xuiocodex
andcommitted
Fix CI app-server hardening races
Preserve malformed app-server startup output until the active turn is installed. Signal both child process groups and direct children, and allow SIGKILL after an ignored SIGTERM. Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent 6b0975c commit 7e1bfc5

3 files changed

Lines changed: 44 additions & 17 deletions

File tree

dist/index.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21536,11 +21536,10 @@ var trackedChildren = /* @__PURE__ */ new Map();
2153621536
var cleanupHandlers = /* @__PURE__ */ new Set();
2153721537
var cleanupPromise;
2153821538
function killChildProcess(child, signal) {
21539-
if (child.exitCode !== null || child.killed) return;
21539+
if (child.exitCode !== null) return;
2154021540
try {
2154121541
if (process.platform !== "win32" && child.pid) {
2154221542
process.kill(-child.pid, signal);
21543-
return;
2154421543
}
2154521544
} catch {
2154621545
}
@@ -23996,13 +23995,14 @@ var CodexAppServerSession = class _CodexAppServerSession {
2399623995
message = JSON.parse(line);
2399723996
} catch {
2399823997
const error2 = `Unparseable Codex app-server line: ${line.slice(0, 500)}`;
23999-
this.lastError = error2;
24000-
this.activeTurn?.summary.errors.push(error2);
24001-
this.activeTurn?.stdout.append(`${line}
24002-
`);
24003-
this.activeTurn?.artifactWriter.appendStdout(`${line}
24004-
`);
24005-
this.activeTurn?.publishSnapshot(true);
23998+
if (!this.activeTurn && this.acceptingStartNotifications) {
23999+
this.queuePendingStartNotification({
24000+
method: "internal/unparseableLine",
24001+
params: { error: error2, line }
24002+
});
24003+
return;
24004+
}
24005+
this.recordUnparseableLine(error2, line);
2400624006
return;
2400724007
}
2400824008
const id = typeof message.id === "string" || typeof message.id === "number" ? String(message.id) : void 0;
@@ -24063,6 +24063,10 @@ var CodexAppServerSession = class _CodexAppServerSession {
2406324063
if (this.acceptingStartNotifications) this.queuePendingStartNotification(message);
2406424064
return;
2406524065
}
24066+
if (method === "internal/unparseableLine" && typeof params?.error === "string") {
24067+
this.recordUnparseableLine(params.error, typeof params.line === "string" ? params.line : "");
24068+
return;
24069+
}
2406624070
for (const handler of this.notificationHandlers) handler(message);
2406724071
active.stdout.append(`${JSON.stringify(message)}
2406824072
`);
@@ -24177,6 +24181,15 @@ var CodexAppServerSession = class _CodexAppServerSession {
2417724181
this.activeTurn = void 0;
2417824182
}
2417924183
}
24184+
recordUnparseableLine(error2, line) {
24185+
this.lastError = error2;
24186+
this.activeTurn?.summary.errors.push(error2);
24187+
this.activeTurn?.stdout.append(`${line}
24188+
`);
24189+
this.activeTurn?.artifactWriter.appendStdout(`${line}
24190+
`);
24191+
this.activeTurn?.publishSnapshot(true);
24192+
}
2418024193
async probeThreadRead(timeoutMs) {
2418124194
try {
2418224195
await this.request("thread/read", {

src/app-server.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,14 @@ export class CodexAppServerSession {
724724
message = JSON.parse(line) as JsonObject;
725725
} catch {
726726
const error = `Unparseable Codex app-server line: ${line.slice(0, 500)}`;
727-
this.lastError = error;
728-
this.activeTurn?.summary.errors.push(error);
729-
this.activeTurn?.stdout.append(`${line}\n`);
730-
this.activeTurn?.artifactWriter.appendStdout(`${line}\n`);
731-
this.activeTurn?.publishSnapshot(true);
727+
if (!this.activeTurn && this.acceptingStartNotifications) {
728+
this.queuePendingStartNotification({
729+
method: "internal/unparseableLine",
730+
params: { error, line },
731+
});
732+
return;
733+
}
734+
this.recordUnparseableLine(error, line);
732735
return;
733736
}
734737

@@ -802,6 +805,10 @@ export class CodexAppServerSession {
802805
if (this.acceptingStartNotifications) this.queuePendingStartNotification(message);
803806
return;
804807
}
808+
if (method === "internal/unparseableLine" && typeof params?.error === "string") {
809+
this.recordUnparseableLine(params.error, typeof params.line === "string" ? params.line : "");
810+
return;
811+
}
805812
for (const handler of this.notificationHandlers) handler(message);
806813
active.stdout.append(`${JSON.stringify(message)}\n`);
807814
active.artifactWriter.appendStdout(`${JSON.stringify(message)}\n`);
@@ -928,6 +935,14 @@ export class CodexAppServerSession {
928935
}
929936
}
930937

938+
private recordUnparseableLine(error: string, line: string): void {
939+
this.lastError = error;
940+
this.activeTurn?.summary.errors.push(error);
941+
this.activeTurn?.stdout.append(`${line}\n`);
942+
this.activeTurn?.artifactWriter.appendStdout(`${line}\n`);
943+
this.activeTurn?.publishSnapshot(true);
944+
}
945+
931946
private async probeThreadRead(timeoutMs: number): Promise<void> {
932947
try {
933948
await this.request("thread/read", {

src/lifecycle.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ const cleanupHandlers = new Set<CleanupHandler>();
88
let cleanupPromise: Promise<void> | undefined;
99

1010
export function killChildProcess(child: ChildProcess, signal: NodeJS.Signals): void {
11-
if (child.exitCode !== null || child.killed) return;
11+
if (child.exitCode !== null) return;
1212

1313
try {
1414
if (process.platform !== "win32" && child.pid) {
1515
process.kill(-child.pid, signal);
16-
return;
1716
}
1817
} catch {
19-
// Fall back to killing the direct child below.
18+
// Direct-child signaling below is still attempted.
2019
}
2120

2221
try {

0 commit comments

Comments
 (0)