Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6af7cf5
Add Codex launch arguments setting
jamesx0416 Jun 1, 2026
8d4883a
Address Codex launch args review
jamesx0416 Jun 1, 2026
e9608d9
Keep Codex launch args PR small
jamesx0416 Jun 1, 2026
e051516
Fix Codex adapter launch args test expectation
jamesx0416 Jun 1, 2026
3d522fe
Forward shared Codex launch args to exec
jamesx0416 Jun 4, 2026
4f301a6
Merge branch 'main' into t3code/codex-launch-args
jamesx0416 Jun 4, 2026
8939411
Merge branch 'main' into t3code/codex-launch-args
jamesx0416 Jun 4, 2026
c714c38
Handle incomplete Codex exec launch flags
jamesx0416 Jun 4, 2026
15cbb46
Merge branch 'main' into t3code/codex-launch-args
juliusmarminge Jun 5, 2026
518e65d
Change test framework import from vitest to vite-plus
juliusmarminge Jun 5, 2026
737d8f6
Merge upstream/main into pr-2892
jamesx0416 Jun 20, 2026
9fcde3c
Merge branch 'main' into t3code/codex-launch-args
juliusmarminge Jun 20, 2026
0d5061a
Fix Codex app-server args
jamesx0416 Jun 21, 2026
7867442
Merge branch 'main' into t3code/codex-launch-args
jamesx0416 Jun 21, 2026
7b3b6d3
Fix Codex launch args checks
jamesx0416 Jun 21, 2026
006c9f5
Merge branch 'main' into t3code/codex-launch-args
jamesx0416 Jun 29, 2026
ccfabb2
Add codex launch args env override
invalid-email-address Jun 30, 2026
5304b45
Preserve launch args for MCP Codex sessions
invalid-email-address Jun 30, 2026
036a8dd
Parse quoted Codex launch args
invalid-email-address Jul 1, 2026
568ae0e
Preserve backslashes in Codex launch args
invalid-email-address Jul 1, 2026
bfa7715
Resolve catalog store merge conflict
jamesx0416 Jul 10, 2026
9dbbf9e
Merge branch 'main' into t3code/codex-launch-args
jamesx0416 Jul 10, 2026
04cb6cd
Share quote-aware CLI arg tokenization
jamesx0416 Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ validationLayer("CodexAdapterLive validation", (it) => {
NodeAssert.deepStrictEqual(validationRuntimeFactory.factory.mock.calls[0]?.[0], {
binaryPath: "codex",
cwd: process.cwd(),
launchArgs: "",
model: "gpt-5.3-codex",
providerInstanceId: ProviderInstanceId.make("codex"),
serviceTier: "priority",
Expand Down Expand Up @@ -359,6 +360,69 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
}),
);

it.effect("passes configured launch args into the session runtime", () => {
const runtimeFactory = makeRuntimeFactory();
const layer = Layer.effect(
CodexAdapter,
Effect.gen(function* () {
const codexConfig = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" });
return yield* makeCodexAdapter(codexConfig, {
makeRuntime: runtimeFactory.factory,
});
}),
).pipe(
Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);

return Effect.gen(function* () {
const adapter = yield* CodexAdapter;
yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId: asThreadId("sess-launch-args"),
runtimeMode: "full-access",
});

const runtime = runtimeFactory.lastRuntime;
NodeAssert.ok(runtime);
NodeAssert.equal(runtime.options.launchArgs, "--strict-config --enable foo");
}).pipe(Effect.provide(layer));
});

it.effect("uses T3CODE_CODEX_LAUNCH_ARGS for the session runtime", () => {
const runtimeFactory = makeRuntimeFactory();
const layer = Layer.effect(
CodexAdapter,
Effect.gen(function* () {
const codexConfig = decodeCodexSettings({ launchArgs: "--enable settings-feature" });
return yield* makeCodexAdapter(codexConfig, {
environment: { T3CODE_CODEX_LAUNCH_ARGS: " --strict-config --enable env-feature " },
makeRuntime: runtimeFactory.factory,
});
}),
).pipe(
Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);

return Effect.gen(function* () {
const adapter = yield* CodexAdapter;
yield* adapter.startSession({
provider: ProviderDriverKind.make("codex"),
threadId: asThreadId("sess-launch-args-env"),
runtimeMode: "full-access",
});

const runtime = runtimeFactory.lastRuntime;
NodeAssert.ok(runtime);
NodeAssert.equal(runtime.options.launchArgs, "--strict-config --enable env-feature");
}).pipe(Effect.provide(layer));
});

it.effect("maps codex model options for the adapter's bound custom instance id", () => {
const customInstanceId = ProviderInstanceId.make("codex_personal");
const customRuntimeFactory = makeRuntimeFactory();
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.ts
Comment thread
jamesx0416 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
type CodexSessionRuntimeShape,
} from "./CodexSessionRuntime.ts";
import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts";
import { resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";
const isCodexAppServerProcessExitedError = Schema.is(CodexErrors.CodexAppServerProcessExitedError);
const isCodexAppServerTransportError = Schema.is(CodexErrors.CodexAppServerTransportError);
const isCodexSessionRuntimeThreadIdMissingError = Schema.is(
Expand Down Expand Up @@ -1392,6 +1393,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
providerInstanceId: boundInstanceId,
cwd: input.cwd ?? process.cwd(),
binaryPath: codexConfig.binaryPath,
launchArgs: resolveCodexLaunchArgs(codexConfig.launchArgs, options?.environment),
...(options?.environment ? { environment: options.environment } : {}),
...(codexConfig.homePath ? { homePath: codexConfig.homePath } : {}),
...(isCodexResumeCursorSchema(input.resumeCursor)
Expand Down
16 changes: 12 additions & 4 deletions apps/server/src/provider/Layers/CodexProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ServerSettingsError } from "@t3tools/contracts";

import { createModelCapabilities } from "@t3tools/shared/model";
import { resolveSpawnCommand } from "@t3tools/shared/shell";
import { codexAppServerArgs, resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";
import {
AUTH_PROBE_TIMEOUT_MS,
buildServerProvider,
Expand Down Expand Up @@ -289,6 +290,7 @@ export function buildCodexInitializeParams(): CodexSchema.V1InitializeParams {
const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(function* (input: {
readonly binaryPath: string;
readonly homePath?: string;
readonly launchArgs?: string;
readonly cwd: string;
readonly customModels?: ReadonlyArray<string>;
readonly environment?: NodeJS.ProcessEnv;
Expand All @@ -303,10 +305,14 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
...input.environment,
...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}),
};
const spawnCommand = yield* resolveSpawnCommand(input.binaryPath, ["app-server"], {
env: environment,
extendEnv: true,
});
const spawnCommand = yield* resolveSpawnCommand(
input.binaryPath,
codexAppServerArgs(input.launchArgs),
{
env: environment,
extendEnv: true,
},
);
const child = yield* spawner
.spawn(
ChildProcess.make(spawnCommand.command, spawnCommand.args, {
Expand Down Expand Up @@ -465,6 +471,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
probe: (input: {
readonly binaryPath: string;
readonly homePath?: string;
readonly launchArgs?: string;
readonly cwd: string;
readonly customModels: ReadonlyArray<string>;
readonly environment?: NodeJS.ProcessEnv;
Expand Down Expand Up @@ -503,6 +510,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
const probeResult = yield* probe({
binaryPath: codexSettings.binaryPath,
homePath: codexSettings.homePath,
launchArgs: resolveCodexLaunchArgs(codexSettings.launchArgs, resolvedEnvironment),
cwd: process.cwd(),
customModels: codexSettings.customModels,
environment: resolvedEnvironment,
Expand Down
28 changes: 28 additions & 0 deletions apps/server/src/provider/Layers/CodexSessionRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
} from "../CodexDeveloperInstructions.ts";
import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts";
import {
buildTurnStartParams,
hasConfiguredMcpServer,
Expand Down Expand Up @@ -219,6 +220,33 @@ describe("hasConfiguredMcpServer", () => {
});
});

describe("codexSessionAppServerArgs", () => {
it("keeps the app-server subcommand when explicit args are provided", () => {
NodeAssert.deepStrictEqual(codexSessionAppServerArgs(["-c", "model=gpt-5"], undefined), [
"app-server",
"-c",
"model=gpt-5",
]);
});

it("keeps launch args when explicit app-server args are provided", () => {
NodeAssert.deepStrictEqual(
codexSessionAppServerArgs(
["-c", "mcp_servers.t3-code.url=http://127.0.0.1/mcp"],
"--strict-config --enable foo",
),
[
"app-server",
"--strict-config",
"--enable",
"foo",
"-c",
"mcp_servers.t3-code.url=http://127.0.0.1/mcp",
],
);
});
});

describe("isRecoverableThreadResumeError", () => {
it("matches missing thread errors", () => {
NodeAssert.equal(
Expand Down
12 changes: 7 additions & 5 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import * as CodexRpc from "effect-codex-app-server/rpc";
import * as EffectCodexSchema from "effect-codex-app-server/schema";

import { buildCodexInitializeParams } from "./CodexProvider.ts";
import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts";
import { expandHomePath } from "../../pathExpansion.ts";
import {
CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
Expand Down Expand Up @@ -100,6 +101,7 @@ export interface CodexSessionRuntimeOptions {
readonly providerInstanceId?: ProviderInstanceId;
readonly binaryPath: string;
readonly homePath?: string;
readonly launchArgs?: string;
readonly environment?: NodeJS.ProcessEnv;
readonly cwd: string;
readonly runtimeMode: RuntimeMode;
Expand Down Expand Up @@ -719,11 +721,11 @@ export const makeCodexSessionRuntime = (
...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}),
};
const extendEnv = options.environment === undefined;
const spawnCommand = yield* resolveSpawnCommand(
options.binaryPath,
["app-server", ...(options.appServerArgs ?? [])],
{ env, extendEnv },
);
const appServerArgs = codexSessionAppServerArgs(options.appServerArgs, options.launchArgs);
const spawnCommand = yield* resolveSpawnCommand(options.binaryPath, appServerArgs, {
env,
extendEnv,
});
const child = yield* spawner
.spawn(
ChildProcess.make(spawnCommand.command, spawnCommand.args, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const makeCodexConfig = (overrides: Partial<CodexSettings>): CodexSettings => ({
binaryPath: "codex",
homePath: "",
shadowHomePath: "",
launchArgs: "",
customModels: [],
...overrides,
});
Expand Down
16 changes: 16 additions & 0 deletions apps/server/src/provider/Layers/ProviderRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const encodedDefaultServerSettings = encodeServerSettings(DEFAULT_SERVER_SETTING

const defaultClaudeSettings: ClaudeSettings = Schema.decodeSync(ClaudeSettings)({});
const defaultCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({});
const decodeCodexSettings = Schema.decodeSync(CodexSettings);
const disabledCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({
enabled: false,
});
Expand Down Expand Up @@ -346,6 +347,21 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
}),
);

it.effect("passes configured launch args to the Codex provider probe", () =>
Effect.gen(function* () {
let observedLaunchArgs: string | undefined;
const settings = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" });

const status = yield* checkCodexProviderStatus(settings, (input) => {
observedLaunchArgs = input.launchArgs;
return Effect.succeed(makeCodexProbeSnapshot());
});

assert.strictEqual(status.status, "ready");
assert.strictEqual(observedLaunchArgs, "--strict-config --enable foo");
}),
);

it.effect("returns unauthenticated when app-server requires OpenAI auth", () =>
Effect.gen(function* () {
const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
Expand Down
59 changes: 59 additions & 0 deletions apps/server/src/provider/Layers/codexLaunchArgs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as NodeAssert from "node:assert/strict";

import { describe, it } from "vite-plus/test";

import {
codexAppServerArgs,
codexExecLaunchArgs,
resolveCodexLaunchArgs,
} from "./codexLaunchArgs.ts";

describe("resolveCodexLaunchArgs", () => {
it("uses T3CODE_CODEX_LAUNCH_ARGS before configured settings", () => {
NodeAssert.equal(
resolveCodexLaunchArgs(" --strict-config ", { T3CODE_CODEX_LAUNCH_ARGS: "--enable foo" }),
"--enable foo",
);
});

it("uses configured settings when T3CODE_CODEX_LAUNCH_ARGS is empty", () => {
NodeAssert.equal(
resolveCodexLaunchArgs(" --strict-config ", { T3CODE_CODEX_LAUNCH_ARGS: " " }),
"--strict-config",
);
});

it("ignores whitespace-only environment values", () => {
NodeAssert.equal(resolveCodexLaunchArgs("", { T3CODE_CODEX_LAUNCH_ARGS: " " }), "");
});
});

describe("codexAppServerArgs", () => {
it("returns the app-server command for empty launch args", () => {
NodeAssert.deepStrictEqual(codexAppServerArgs(""), ["app-server"]);
});

it("appends parsed launch args after app-server", () => {
NodeAssert.deepStrictEqual(codexAppServerArgs("--strict-config --enable foo"), [
"app-server",
"--strict-config",
"--enable",
"foo",
]);
});
});

describe("codexExecLaunchArgs", () => {
it("keeps shared codex flags and omits app-server-only flags", () => {
NodeAssert.deepStrictEqual(
codexExecLaunchArgs('--strict-config --enable foo --listen off --config model="gpt 5"'),
["--strict-config", "--enable", "foo", "--config", "model=gpt 5"],
);
});

it("does not pair value-taking flags with adjacent flags", () => {
NodeAssert.deepStrictEqual(codexExecLaunchArgs("--config --strict-config --enable --disable"), [
"--strict-config",
]);
});
});
48 changes: 48 additions & 0 deletions apps/server/src/provider/Layers/codexLaunchArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { tokenizeCliArgs } from "@t3tools/shared/cliArgs";

export const T3CODE_CODEX_LAUNCH_ARGS_ENV = "T3CODE_CODEX_LAUNCH_ARGS";

export const resolveCodexLaunchArgs = (
launchArgs?: string,
environment: NodeJS.ProcessEnv = process.env,
) => environment[T3CODE_CODEX_LAUNCH_ARGS_ENV]?.trim() || launchArgs?.trim() || "";

export const codexLaunchArgv = (launchArgs?: string): ReadonlyArray<string> =>
tokenizeCliArgs(launchArgs);

export const codexAppServerArgs = (launchArgs?: string) => [
"app-server",
...codexLaunchArgv(launchArgs),
];

export const codexExecLaunchArgs = (launchArgs?: string) => {
const args = codexLaunchArgv(launchArgs);
const execArgs: Array<string> = [];

for (let index = 0; index < args.length; index++) {
const arg = args[index];
if (arg === undefined) continue;

if (arg === "--strict-config" || arg.startsWith("--config=") || arg.startsWith("-c=")) {
execArgs.push(arg);
} else if (arg === "--config" || arg === "-c" || arg === "--enable" || arg === "--disable") {
const value = args[index + 1];
if (value !== undefined && !value.startsWith("-")) {
execArgs.push(arg, value);
index++;
}
} else if (arg.startsWith("--enable=") || arg.startsWith("--disable=")) {
execArgs.push(arg);
}
}

return execArgs;
};

export const codexSessionAppServerArgs = (
appServerArgs: ReadonlyArray<string> | undefined,
launchArgs: string | undefined,
) => {
const launchAppServerArgs = codexAppServerArgs(launchArgs);
return appServerArgs ? [...launchAppServerArgs, ...appServerArgs] : launchAppServerArgs;
};
2 changes: 2 additions & 0 deletions apps/server/src/serverSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
binaryPath: "/opt/homebrew/bin/codex",
homePath: "/Users/julius/.codex",
shadowHomePath: "",
launchArgs: "",
customModels: [],
});
assert.deepEqual(next.providers.claudeAgent, {
Expand Down Expand Up @@ -420,6 +421,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
binaryPath: "/opt/homebrew/bin/codex",
homePath: "",
shadowHomePath: "",
launchArgs: "",
customModels: [],
});
assert.deepEqual(next.providers.claudeAgent, {
Expand Down
Loading
Loading