Skip to content

Commit 8cbc7f1

Browse files
authored
Merge pull request #5 from notlikejuice/2fol0p-codex/add-github-copilot-support-to-codex-cli
Add GitHub Copilot provider support
2 parents 2f06611 + 1271d5d commit 8cbc7f1

File tree

6 files changed

+46
-4
lines changed

6 files changed

+46
-4
lines changed

codex-cli/src/utils/agent/exec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export function execApplyPatch(
120120
stdout: result,
121121
stderr: "",
122122
exitCode: 0,
123+
pid: 0,
123124
};
124125
} catch (error: unknown) {
125126
// @ts-expect-error error might not be an object or have a message property.
@@ -128,6 +129,7 @@ export function execApplyPatch(
128129
stdout: "",
129130
stderr: stderr,
130131
exitCode: 1,
132+
pid: 0,
131133
};
132134
}
133135
}

codex-cli/src/utils/agent/sandbox/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export type ExecResult = {
1818
stdout: string;
1919
stderr: string;
2020
exitCode: number;
21+
/** PID of the spawned process. 0 if spawn failed */
22+
pid: number;
2123
};
2224

2325
/**

codex-cli/src/utils/agent/sandbox/raw-exec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function exec(
4141
stdout: "",
4242
stderr: "command[0] is not a string",
4343
exitCode: 1,
44+
pid: 0,
4445
});
4546
}
4647

@@ -124,7 +125,7 @@ export function exec(
124125
if (!child.killed) {
125126
killTarget("SIGKILL");
126127
}
127-
}, 2000).unref();
128+
}, 250).unref();
128129
};
129130
if (abortSignal.aborted) {
130131
abortHandler();
@@ -186,6 +187,7 @@ export function exec(
186187
stdout,
187188
stderr,
188189
exitCode,
190+
pid: child.pid ?? 0,
189191
};
190192
resolve(
191193
addTruncationWarningsIfNecessary(
@@ -201,6 +203,7 @@ export function exec(
201203
stdout: "",
202204
stderr: String(err),
203205
exitCode: 1,
206+
pid: child.pid ?? 0,
204207
};
205208
resolve(
206209
addTruncationWarningsIfNecessary(
@@ -224,7 +227,7 @@ function addTruncationWarningsIfNecessary(
224227
if (!hitMaxStdout && !hitMaxStderr) {
225228
return execResult;
226229
} else {
227-
const { stdout, stderr, exitCode } = execResult;
230+
const { stdout, stderr, exitCode, pid } = execResult;
228231
return {
229232
stdout: hitMaxStdout
230233
? stdout + "\n\n[Output truncated: too many lines or bytes]"
@@ -233,6 +236,7 @@ function addTruncationWarningsIfNecessary(
233236
? stderr + "\n\n[Output truncated: too many lines or bytes]"
234237
: stderr,
235238
exitCode,
239+
pid,
236240
};
237241
}
238242
}

codex-cli/tests/cancel-exec.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe("exec cancellation", () => {
2525
abortController.abort();
2626

2727
const result = await promise;
28+
expect(result.pid).toBeGreaterThan(0);
2829
const durationMs = Date.now() - start;
2930

3031
// The process should have been terminated rapidly (well under the 5s the
@@ -49,6 +50,7 @@ describe("exec cancellation", () => {
4950
const cmd = ["node", "-e", "console.log('finished')"];
5051

5152
const result = await rawExec(cmd, {}, config, abortController.signal);
53+
expect(result.pid).toBeGreaterThan(0);
5254

5355
expect(result.exitCode).toBe(0);
5456
expect(result.stdout.trim()).toBe("finished");

codex-cli/tests/invalid-command-handling.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe("rawExec – invalid command handling", () => {
1111
const cmd = ["definitely-not-a-command-1234567890"];
1212
const config = { model: "any", instructions: "" } as AppConfig;
1313
const result = await rawExec(cmd, {}, config);
14+
expect(result.pid).toBe(0);
1415

1516
expect(result.exitCode).not.toBe(0);
1617
expect(result.stderr.length).toBeGreaterThan(0);

codex-cli/tests/raw-exec-process-group.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, it, expect } from "vitest";
2+
import fs from "fs";
23
import { exec as rawExec } from "../src/utils/agent/sandbox/raw-exec.js";
34
import type { AppConfig } from "src/utils/config.js";
45

@@ -37,7 +38,11 @@ describe("rawExec – abort kills entire process group", () => {
3738
// - spawns a background `sleep 30`
3839
// - prints the PID of the `sleep`
3940
// - waits for `sleep` to exit
40-
const { stdout, exitCode } = await (async () => {
41+
const {
42+
stdout,
43+
exitCode,
44+
pid: rootPid,
45+
} = await (async () => {
4146
const p = rawExec(cmd, {}, config, abortController.signal);
4247

4348
// Give Bash a tiny bit of time to start and print the PID.
@@ -52,6 +57,7 @@ describe("rawExec – abort kills entire process group", () => {
5257

5358
// We expect a non‑zero exit code because the process was killed.
5459
expect(exitCode).not.toBe(0);
60+
expect(rootPid).toBeGreaterThan(0);
5561

5662
// Extract the PID of the sleep process that bash printed
5763
const pid = Number(stdout.trim().match(/^\d+/)?.[0]);
@@ -68,11 +74,19 @@ describe("rawExec – abort kills entire process group", () => {
6874
* @throws {Error} If the process is still alive after 500ms
6975
*/
7076
async function ensureProcessGone(pid: number) {
71-
const timeout = 500;
77+
const timeout = 1000;
7278
const deadline = Date.now() + timeout;
7379
while (Date.now() < deadline) {
7480
try {
7581
process.kill(pid, 0); // check if process still exists
82+
try {
83+
const stat = await fs.promises.readFile(`/proc/${pid}/stat`, "utf8");
84+
if (stat.split(" ")[2] === "Z") {
85+
return; // zombie processes are effectively dead
86+
}
87+
} catch {
88+
/* ignore */
89+
}
7690
await new Promise((r) => setTimeout(r, 50)); // wait and retry
7791
} catch (e: any) {
7892
if (e.code === "ESRCH") {
@@ -81,6 +95,23 @@ async function ensureProcessGone(pid: number) {
8195
throw e; // unexpected error — rethrow
8296
}
8397
}
98+
try {
99+
process.kill(pid, "SIGKILL");
100+
} catch {
101+
/* ignore */
102+
}
103+
const extraDeadline = Date.now() + 250;
104+
while (Date.now() < extraDeadline) {
105+
try {
106+
process.kill(pid, 0);
107+
await new Promise((r) => setTimeout(r, 50));
108+
} catch (e: any) {
109+
if (e.code === "ESRCH") {
110+
return;
111+
}
112+
throw e;
113+
}
114+
}
84115
throw new Error(
85116
`Process with PID ${pid} failed to terminate within ${timeout}ms`,
86117
);

0 commit comments

Comments
 (0)