authorgravatar for julius0216@outlook.comjulius <julius0216@outlook.com> 2026-03-04 04:14:42-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-03 21:03:16-08:00
log0a1233344896376ec8b31b79ee65500eeef3a09e
tree6fe353c00b92f7281e82d96d3371d4421ebf671e
parent9db54c9b19b136526baa97677e244b4b0071ee5b

Fix Codex process spawning and health checks on Windows

- Run Codex-related child processes with `shell` on Windows for reliable command execution - Switch provider auth probe to `codex login status` and update health-check expectations - Use Node PTY on Windows instead of Bun PTY in server runtime setup

7 files changed, 20 insertions(+), 37 deletions(-)

apps/server/src/codexAppServerManager.ts+1
......@@ -185,6 +185,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
185185 ...(codexHomePath ? { CODEX_HOME: codexHomePath } : {}),
186186 },
187187 stdio: ["pipe", "pipe", "pipe"],
188 shell: process.platform === "win32",
188189 });
189190 const output = readline.createInterface({ input: child.stdout });
190191
apps/server/src/git/Layers/CodexTextGeneration.ts+1
......@@ -224,6 +224,7 @@ const makeCodexTextGeneration = Effect.gen(function* () {
224224 ],
225225 {
226226 cwd,
227 shell: process.platform === "win32",
227228 stdin: {
228229 stream: Stream.make(new TextEncoder().encode(prompt)),
229230 },
apps/server/src/open.ts+1
......@@ -230,6 +230,7 @@ export const launchDetached = (launch: EditorLaunch) =>
230230 child = spawn(launch.command, [...launch.args], {
231231 detached: true,
232232 stdio: "ignore",
233 shell: process.platform === "win32",
233234 });
234235 } catch (error) {
235236 return resume(
apps/server/src/processRunner.ts+1
......@@ -112,6 +112,7 @@ export async function runProcess(
112112 cwd: options.cwd,
113113 env: options.env,
114114 stdio: "pipe",
115 shell: process.platform === "win32",
115116 });
116117
117118 let stdout = "";
apps/server/src/provider/Layers/ProviderHealth.test.ts+10-35
......@@ -22,8 +22,8 @@ describe("checkProviderStatusesOnStartup", () => {
2222 if (args.join(" ") === "--version") {
2323 return result({ stdout: "codex 1.0.0\n" });
2424 }
25 if (args.join(" ") === "auth status --json") {
26 return result({ stdout: '{"authenticated":true}\n' });
25 if (args.join(" ") === "login status") {
26 return result({ stdout: "Logged in\n" });
2727 }
2828 throw new Error(`Unexpected command: ${args.join(" ")}`);
2929 });
......@@ -59,7 +59,7 @@ describe("checkProviderStatusesOnStartup", () => {
5959 if (args.join(" ") === "--version") {
6060 return result({ stdout: "codex 1.0.0\n" });
6161 }
62 if (args.join(" ") === "auth status --json") {
62 if (args.join(" ") === "login status") {
6363 return result({
6464 code: 1,
6565 stderr: "Not logged in. Run codex login.",
......@@ -79,14 +79,15 @@ describe("checkProviderStatusesOnStartup", () => {
7979 ]);
8080 });
8181
82 it("returns unauthenticated when auth probe JSON array includes authenticated=false", async () => {
82 it("returns unauthenticated when login status output includes 'not logged in'", async () => {
8383 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
8484 if (args.join(" ") === "--version") {
8585 return result({ stdout: "codex 1.0.0\n" });
8686 }
87 if (args.join(" ") === "auth status --json") {
87 if (args.join(" ") === "login status") {
8888 return result({
89 stdout: '[{"authenticated":false}]\n',
89 code: 1,
90 stdout: "Not logged in\n",
9091 });
9192 }
9293 throw new Error(`Unexpected command: ${args.join(" ")}`);
......@@ -103,41 +104,15 @@ describe("checkProviderStatusesOnStartup", () => {
103104 ]);
104105 });
105106
106 it("returns warning when auth probe JSON array has no auth marker", async () => {
107 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
108 if (args.join(" ") === "--version") {
109 return result({ stdout: "codex 1.0.0\n" });
110 }
111 if (args.join(" ") === "auth status --json") {
112 return result({
113 code: 0,
114 stdout: '[{"ok":true}]\n',
115 });
116 }
117 throw new Error(`Unexpected command: ${args.join(" ")}`);
118 });
119
120 expect(status).toEqual([
121 expect.objectContaining({
122 provider: "codex",
123 status: "warning",
124 available: true,
125 authStatus: "unknown",
126 message:
127 "Could not verify Codex authentication status from JSON output (missing auth marker).",
128 }),
129 ]);
130 });
131
132 it("returns warning when auth status command is unsupported", async () => {
107 it("returns warning when login status command is unsupported", async () => {
133108 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
134109 if (args.join(" ") === "--version") {
135110 return result({ stdout: "codex 1.0.0\n" });
136111 }
137 if (args.join(" ") === "auth status --json") {
112 if (args.join(" ") === "login status") {
138113 return result({
139114 code: 2,
140 stderr: "error: unknown command 'auth'",
115 stderr: "error: unknown command 'login'",
141116 });
142117 }
143118 throw new Error(`Unexpected command: ${args.join(" ")}`);
apps/server/src/provider/Layers/ProviderHealth.ts+1-1
......@@ -223,7 +223,7 @@ async function checkCodexProviderStatus(run: CommandRunner): Promise<ServerProvi
223223 }
224224
225225 try {
226 const auth = await run("codex", ["auth", "status", "--json"], {
226 const auth = await run("codex", ["login", "status"], {
227227 timeoutMs: DEFAULT_TIMEOUT_MS,
228228 allowNonZeroExit: true,
229229 outputMode: "truncate",
apps/server/src/serverLayers.ts+5-1
......@@ -105,7 +105,11 @@ export function makeServerRuntimeServicesLayer() {
105105 );
106106
107107 const terminalLayer = TerminalManagerLive.pipe(
108 Layer.provide(typeof Bun !== "undefined" ? BunPtyAdapterLive : NodePtyAdapterLive),
108 Layer.provide(
109 typeof Bun !== "undefined" && process.platform !== "win32"
110 ? BunPtyAdapterLive
111 : NodePtyAdapterLive,
112 ),
109113 );
110114
111115 const gitManagerLayer = GitManagerLive.pipe(