From a8f5d969b82c6c06ecdc00e7fef4a044bae71960 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:53:43 +0200 Subject: [PATCH] fix(codex): keep the default effort after a daemon restart (#5686) A Codex agent created without a thinking option ran on the configured default effort until the daemon restarted. The resumed session never resolved the defaults, so it showed thinking auto and sent turns with no effort. Resolve unset model and effort for a resumed thread the same way a new thread does. Closes #5673 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../providers/codex-app-server-agent.test.ts | 30 +++++++++++++++++++ .../agent/providers/codex-app-server-agent.ts | 14 +++++++-- 2 files changed, 41 insertions(+), 3 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 2c74912a47..d55a0b2a79 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 @@ -1170,6 +1170,36 @@ describe("Codex app-server provider", () => { } }); + test("keeps the default effort when a session without one is resumed after a restart", async () => { + const beforeRestart = createFakeCodexAppServer(); + const firstSession = await createProviderWithFakeAppServer(beforeRestart).createSession( + createConfig({ thinkingOptionId: undefined }), + ); + await firstSession.startTurn("first turn"); + await expect(beforeRestart.waitForTurnStart()).resolves.toMatchObject({ effort: "medium" }); + const handle = firstSession.describePersistence()!; + await firstSession.close(); + + const afterRestart = createFakeCodexAppServer(); + // The daemon resumes with the stored agent config, which never recorded an effort. + const resumed = await createProviderWithFakeAppServer(afterRestart).resumeSession(handle, { + model: "gpt-5.4", + thinkingOptionId: undefined, + }); + + try { + await expect(resumed.getRuntimeInfo()).resolves.toMatchObject({ + thinkingOptionId: "medium", + }); + await resumed.startTurn("turn after restart"); + await expect(afterRestart.waitForTurnStart()).resolves.toMatchObject({ effort: "medium" }); + beforeRestart.assertNoErrors(); + afterRestart.assertNoErrors(); + } finally { + await resumed.close(); + } + }); + test("preapproves only granted tools on the injected Codex MCP server", async () => { const session = createSession({ modeId: undefined, 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 f16c5fb6d8..f86d5de653 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 @@ -3611,6 +3611,7 @@ export class CodexAppServerAgentSession implements AgentSession { if (this.currentThreadId) { await this.ensureThreadLoaded(); await this.loadPersistedHistory(this.client); + await this.applyDefaultModelAndThinking(); } if (this.closed) { @@ -5208,13 +5209,20 @@ export class CodexAppServerAgentSession implements AgentSession { return { model, thinkingOptionId }; } + // A session without a model or effort runs on Codex's configured defaults, whether its + // thread is new or resumed after a restart. + private async applyDefaultModelAndThinking(): Promise { + const { model, thinkingOptionId } = await this.resolveModelAndThinking(); + this.config.model = model; + this.config.thinkingOptionId = thinkingOptionId; + return model; + } + private async ensureThread(): Promise { if (!this.client) return; if (this.currentThreadId) return; - const { model, thinkingOptionId } = await this.resolveModelAndThinking(); - this.config.model = model; - this.config.thinkingOptionId = thinkingOptionId; + const model = await this.applyDefaultModelAndThinking(); const { params, approvalPolicy, sandbox } = this.buildThreadStartRequest(model); const rawResponse = await this.client.request("thread/start", params);