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...@@ -185,6 +185,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
185 ...(codexHomePath ? { CODEX_HOME: codexHomePath } : {}),185 ...(codexHomePath ? { CODEX_HOME: codexHomePath } : {}),
186 },186 },
187 stdio: ["pipe", "pipe", "pipe"],187 stdio: ["pipe", "pipe", "pipe"],
188 shell: process.platform === "win32",
188 });189 });
189 const output = readline.createInterface({ input: child.stdout });190 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* () {...@@ -224,6 +224,7 @@ const makeCodexTextGeneration = Effect.gen(function* () {
224 ],224 ],
225 {225 {
226 cwd,226 cwd,
227 shell: process.platform === "win32",
227 stdin: {228 stdin: {
228 stream: Stream.make(new TextEncoder().encode(prompt)),229 stream: Stream.make(new TextEncoder().encode(prompt)),
229 },230 },
apps/server/src/open.ts+1
...@@ -230,6 +230,7 @@ export const launchDetached = (launch: EditorLaunch) =>...@@ -230,6 +230,7 @@ export const launchDetached = (launch: EditorLaunch) =>
230 child = spawn(launch.command, [...launch.args], {230 child = spawn(launch.command, [...launch.args], {
231 detached: true,231 detached: true,
232 stdio: "ignore",232 stdio: "ignore",
233 shell: process.platform === "win32",
233 });234 });
234 } catch (error) {235 } catch (error) {
235 return resume(236 return resume(
apps/server/src/processRunner.ts+1
...@@ -112,6 +112,7 @@ export async function runProcess(...@@ -112,6 +112,7 @@ export async function runProcess(
112 cwd: options.cwd,112 cwd: options.cwd,
113 env: options.env,113 env: options.env,
114 stdio: "pipe",114 stdio: "pipe",
115 shell: process.platform === "win32",
115 });116 });
116117
117 let stdout = "";118 let stdout = "";
apps/server/src/provider/Layers/ProviderHealth.test.ts+10-35
...@@ -22,8 +22,8 @@ describe("checkProviderStatusesOnStartup", () => {...@@ -22,8 +22,8 @@ describe("checkProviderStatusesOnStartup", () => {
22 if (args.join(" ") === "--version") {22 if (args.join(" ") === "--version") {
23 return result({ stdout: "codex 1.0.0\n" });23 return result({ stdout: "codex 1.0.0\n" });
24 }24 }
25 if (args.join(" ") === "auth status --json") {25 if (args.join(" ") === "login status") {
26 return result({ stdout: '{"authenticated":true}\n' });26 return result({ stdout: "Logged in\n" });
27 }27 }
28 throw new Error(`Unexpected command: ${args.join(" ")}`);28 throw new Error(`Unexpected command: ${args.join(" ")}`);
29 });29 });
...@@ -59,7 +59,7 @@ describe("checkProviderStatusesOnStartup", () => {...@@ -59,7 +59,7 @@ describe("checkProviderStatusesOnStartup", () => {
59 if (args.join(" ") === "--version") {59 if (args.join(" ") === "--version") {
60 return result({ stdout: "codex 1.0.0\n" });60 return result({ stdout: "codex 1.0.0\n" });
61 }61 }
62 if (args.join(" ") === "auth status --json") {62 if (args.join(" ") === "login status") {
63 return result({63 return result({
64 code: 1,64 code: 1,
65 stderr: "Not logged in. Run codex login.",65 stderr: "Not logged in. Run codex login.",
...@@ -79,14 +79,15 @@ describe("checkProviderStatusesOnStartup", () => {...@@ -79,14 +79,15 @@ describe("checkProviderStatusesOnStartup", () => {
79 ]);79 ]);
80 });80 });
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 () => {
83 const status = await checkProviderStatusesOnStartup(async (_command, args) => {83 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
84 if (args.join(" ") === "--version") {84 if (args.join(" ") === "--version") {
85 return result({ stdout: "codex 1.0.0\n" });85 return result({ stdout: "codex 1.0.0\n" });
86 }86 }
87 if (args.join(" ") === "auth status --json") {87 if (args.join(" ") === "login status") {
88 return result({88 return result({
89 stdout: '[{"authenticated":false}]\n',89 code: 1,
90 stdout: "Not logged in\n",
90 });91 });
91 }92 }
92 throw new Error(`Unexpected command: ${args.join(" ")}`);93 throw new Error(`Unexpected command: ${args.join(" ")}`);
...@@ -103,41 +104,15 @@ describe("checkProviderStatusesOnStartup", () => {...@@ -103,41 +104,15 @@ describe("checkProviderStatusesOnStartup", () => {
103 ]);104 ]);
104 });105 });
105106
106 it("returns warning when auth probe JSON array has no auth marker", async () => {107 it("returns warning when login status command is unsupported", 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 () => {
133 const status = await checkProviderStatusesOnStartup(async (_command, args) => {108 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
134 if (args.join(" ") === "--version") {109 if (args.join(" ") === "--version") {
135 return result({ stdout: "codex 1.0.0\n" });110 return result({ stdout: "codex 1.0.0\n" });
136 }111 }
137 if (args.join(" ") === "auth status --json") {112 if (args.join(" ") === "login status") {
138 return result({113 return result({
139 code: 2,114 code: 2,
140 stderr: "error: unknown command 'auth'",115 stderr: "error: unknown command 'login'",
141 });116 });
142 }117 }
143 throw new Error(`Unexpected command: ${args.join(" ")}`);118 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...@@ -223,7 +223,7 @@ async function checkCodexProviderStatus(run: CommandRunner): Promise<ServerProvi
223 }223 }
224224
225 try {225 try {
226 const auth = await run("codex", ["auth", "status", "--json"], {226 const auth = await run("codex", ["login", "status"], {
227 timeoutMs: DEFAULT_TIMEOUT_MS,227 timeoutMs: DEFAULT_TIMEOUT_MS,
228 allowNonZeroExit: true,228 allowNonZeroExit: true,
229 outputMode: "truncate",229 outputMode: "truncate",
apps/server/src/serverLayers.ts+5-1
...@@ -105,7 +105,11 @@ export function makeServerRuntimeServicesLayer() {...@@ -105,7 +105,11 @@ export function makeServerRuntimeServicesLayer() {
105 );105 );
106106
107 const terminalLayer = TerminalManagerLive.pipe(107 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 ),
109 );113 );
110114
111 const gitManagerLayer = GitManagerLive.pipe(115 const gitManagerLayer = GitManagerLive.pipe(