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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add Codex launch arguments setting by jamesx0416 · Pull Request #2892 · pingdotgg/t3code · GitHub
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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