Skip to content

Commit c5f4489

Browse files
committed
Fix Codex probe child process leak on timeout and Object.assign isDefault: undefined
- Add AbortSignal support to probeCodexAccount and use Effect.ensuring in probeCodexCapabilities to kill the child process when the timeout fires, mirroring the existing pattern in probeClaudeCapabilities. - Replace Object.assign with explicit object construction in adjustModelsForSubscription to avoid setting an explicit isDefault: undefined property on non-default context window options.
1 parent 860d096 commit c5f4489

File tree

3 files changed

+19
-3
lines changed

3 files changed

+19
-3
lines changed

apps/server/src/provider/Layers/ClaudeProvider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ export function adjustModelsForSubscription(
333333
capabilities: {
334334
...caps,
335335
contextWindowOptions: caps.contextWindowOptions.map((opt) =>
336-
Object.assign({}, opt, { isDefault: opt.value === "1m" ? true : undefined }),
336+
opt.value === "1m"
337+
? { value: opt.value, label: opt.label, isDefault: true as const }
338+
: { value: opt.value, label: opt.label },
337339
),
338340
},
339341
};

apps/server/src/provider/Layers/CodexProvider.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,22 @@ const CAPABILITIES_PROBE_TIMEOUT_MS = 8_000;
313313
const probeCodexCapabilities = (input: {
314314
readonly binaryPath: string;
315315
readonly homePath?: string;
316-
}) =>
317-
Effect.tryPromise(() => probeCodexAccount(input)).pipe(
316+
}) => {
317+
const abort = new AbortController();
318+
return Effect.tryPromise(() => probeCodexAccount({ ...input, signal: abort.signal })).pipe(
319+
Effect.ensuring(
320+
Effect.sync(() => {
321+
if (!abort.signal.aborted) abort.abort();
322+
}),
323+
),
318324
Effect.timeoutOption(CAPABILITIES_PROBE_TIMEOUT_MS),
319325
Effect.result,
320326
Effect.map((result) => {
321327
if (Result.isFailure(result)) return undefined;
322328
return Option.isSome(result.success) ? result.success.value : undefined;
323329
}),
324330
);
331+
};
325332

326333
const runCodexCommand = (args: ReadonlyArray<string>) =>
327334
Effect.gen(function* () {

apps/server/src/provider/codexAppServer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function killCodexChildProcess(child: ChildProcessWithoutNullStreams): vo
4343
export async function probeCodexAccount(input: {
4444
readonly binaryPath: string;
4545
readonly homePath?: string;
46+
readonly signal?: AbortSignal;
4647
}): Promise<CodexAccountSnapshot> {
4748
return await new Promise((resolve, reject) => {
4849
const child = spawn(input.binaryPath, ["app-server"], {
@@ -82,6 +83,12 @@ export async function probeCodexAccount(input: {
8283
),
8384
);
8485

86+
if (input.signal?.aborted) {
87+
fail(new Error("Codex account probe aborted."));
88+
return;
89+
}
90+
input.signal?.addEventListener("abort", () => fail(new Error("Codex account probe aborted.")));
91+
8592
const writeMessage = (message: unknown) => {
8693
if (!child.stdin.writable) {
8794
fail(new Error("Cannot write to codex app-server stdin."));

0 commit comments

Comments
 (0)