From c6a4ae81c90d9b46b3fbe049ad9b8620f1461689 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 08:09:46 +0200 Subject: [PATCH] fix(codex): start sessions on the project's configured model and effort A session created without a model or reasoning effort read Codex defaults without its cwd, so a trusted project's .codex/config.toml was ignored and the global values were sent as explicit overrides. Read the effective config for the session cwd first and keep getUserSavedConfig as the fallback for older Codex builds. Co-authored-by: 3ae3ae --- .../providers/codex-app-server-agent.test.ts | 36 +++++++++++++ .../agent/providers/codex-app-server-agent.ts | 51 +++++++++++-------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index d4a96b45e6..8437420340 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -1117,6 +1117,42 @@ describe("Codex app-server provider", () => { }); }); + test("starts a session without a model on the project's Codex model and effort", async () => { + const cwd = "/workspace/project"; + const appServer = createFakeCodexAppServer({ + "config/read": (params) => ({ + config: + (params as { cwd?: unknown }).cwd === cwd + ? { model: "project-model", model_reasoning_effort: "high" } + : { model: "global-model", model_reasoning_effort: "low" }, + }), + getUserSavedConfig: () => ({ + config: { model: "global-model", modelReasoningEffort: "low" }, + }), + }); + const session = new CodexAppServerAgentSession( + createConfig({ cwd, model: undefined }), + null, + createTestLogger(), + async () => appServer.child, + ); + + try { + await session.startTurn("use project defaults"); + + await expect(appServer.waitForRequest("thread/start")).resolves.toMatchObject({ + model: "project-model", + }); + await expect(appServer.waitForTurnStart()).resolves.toMatchObject({ + model: "project-model", + effort: "high", + }); + appServer.assertNoErrors(); + } finally { + await session.close(); + } + }); + test("preserves cwd-resolved Codex writable roots under an explicit workflow mode", async () => { const appServer = createFakeCodexAppServer({ "config/read": () => ({ diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 55d7886df8..ac927a8656 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -3100,10 +3100,32 @@ const CodexNotificationSchema = z.union([ ), ]); +// config/read resolves the effective layered config, including a trusted project's +// .codex/config.toml when given its cwd; getUserSavedConfig predates it. async function readCodexConfiguredDefaults( client: CodexAppServerClient, logger: Logger, + cwd: string | null, ): Promise { + let configReadDefaults: CodexConfiguredDefaults = {}; + try { + const response = toObjectRecord(await client.request("config/read", { cwd })); + const config = toObjectRecord(response?.config); + const modelValue = typeof config?.model === "string" ? config.model : undefined; + const thinkingOptionValue = + typeof config?.model_reasoning_effort === "string" ? config.model_reasoning_effort : null; + configReadDefaults = { + model: normalizeCodexModelId(modelValue), + thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue), + }; + } catch (error) { + logger.debug({ error }, "Failed to read Codex config defaults"); + } + + if (configReadDefaults.model && configReadDefaults.thinkingOptionId) { + return configReadDefaults; + } + let savedConfigDefaults: CodexConfiguredDefaults = {}; try { const response = toObjectRecord(await client.request("getUserSavedConfig", {})); @@ -3119,26 +3141,7 @@ async function readCodexConfiguredDefaults( logger.debug({ error }, "Failed to read Codex saved config defaults"); } - if (savedConfigDefaults.model && savedConfigDefaults.thinkingOptionId) { - return savedConfigDefaults; - } - - let configReadDefaults: CodexConfiguredDefaults = {}; - try { - const response = toObjectRecord(await client.request("config/read", {})); - const config = toObjectRecord(response?.config); - const modelValue = typeof config?.model === "string" ? config.model : undefined; - const thinkingOptionValue = - typeof config?.model_reasoning_effort === "string" ? config.model_reasoning_effort : null; - configReadDefaults = { - model: normalizeCodexModelId(modelValue), - thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue), - }; - } catch (error) { - logger.debug({ error }, "Failed to read Codex config defaults"); - } - - return mergeCodexConfiguredDefaults(savedConfigDefaults, configReadDefaults); + return mergeCodexConfiguredDefaults(configReadDefaults, savedConfigDefaults); } interface CodexSkillPromptBlock { @@ -5119,7 +5122,11 @@ export class CodexAppServerAgentSession implements AgentSession { let model = this.config.model; let thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId); if (!model || !thinkingOptionId) { - configuredDefaults = await readCodexConfiguredDefaults(this.client, this.logger); + configuredDefaults = await readCodexConfiguredDefaults( + this.client, + this.logger, + this.config.cwd ?? null, + ); } if (!model) { model = configuredDefaults.model; @@ -7323,7 +7330,7 @@ export class CodexAppServerAgentClient implements AgentClient { const parsedResponse = CodexModelListResponseSchema.safeParse(rawResponse); const models = parsedResponse.success ? (parsedResponse.data.data ?? []) : []; const configuredDefaults = await runProviderRefreshActivity(context, "config/read", () => - readCodexConfiguredDefaults(client!, this.logger), + readCodexConfiguredDefaults(client!, this.logger, null), ); const configuredDefaultModelId = configuredDefaults.model; const configuredDefaultThinkingOptionId = configuredDefaults.thinkingOptionId;