Setup: codex-acp driven over ACP by Kepler (Electron client, spawns the wrapper via dist/index.js and resumes stored sessions with session/load). Reproduced on codex-acp 1.1.4 and 1.1.7, against codex-cli 0.144.6 app-server.
Problem
session/load returns the model and reasoning effort from ~/.codex/config.toml instead of the ones persisted on the thread. Any in-session model/effort switch is silently lost on resume.
The cause is the modelProvider field that loadSession/resumeSession always pass to thread/resume:
// dist/index.js (1.1.7:26277) — same in 1.1.4:26044
const response = await this.codexClient.threadResume({
config: await this.createSessionConfig(...),
cwd: request.cwd,
modelProvider: await this.getResumeModelProvider(), // never absent
threadId: request.sessionId,
});
getResumeModelProvider() is getCurrentModelProvider() ?? "openai", so the field is always populated even with no gateway configured. Passing it makes the app-server discard the thread's stored model/reasoningEffort and re-resolve them from config.
Reproduction
- Create a session and switch it to a model/effort that differs from
~/.codex/config.toml. Mine has model = "gpt-5.5", model_reasoning_effort = "xhigh"; the session was set to gpt-5.6-sol / max.
- Send a prompt. The rollout jsonl records the switch:
"model":"gpt-5.6-sol", "effort":"max" in turn_context.
- Resume that thread with
session/load.
Observed on both versions:
currentModelId= gpt-5.5[xhigh]
model => "gpt-5.5"
reasoning_effort => "xhigh"
Expected gpt-5.6-sol / max (what the thread holds).
Isolating it to modelProvider
Calling thread/resume directly on the app-server for the same thread, varying only which params are sent:
| params sent |
model |
reasoningEffort |
threadId, cwd |
gpt-5.6-sol |
max |
+ config |
gpt-5.6-sol |
max |
+ modelProvider: "openai" |
gpt-5.5 |
xhigh |
+ config + modelProvider (what codex-acp sends) |
gpt-5.5 |
xhigh |
config is irrelevant; modelProvider alone flips it. The values it falls back to are exactly the ones in config.toml.
Expected
Resuming a thread should restore the model and reasoning effort stored on that thread. Either don't send modelProvider on thread/resume when no gateway is configured, or have the app-server keep the thread's model when a provider is passed. If the app-server behaviour is the intended contract, this is arguably an openai/codex bug and I'm happy to move it there.
Impact
The client cannot recover the picks on its own: after a client restart there is no live session left to read them from, and the resume response is the only source of truth. Users see the correct model for a moment (from the client's own cache), then it flips to the config default as the load response lands.
Possibly the same root cause as #336, which reports effort reverting between turns of a live session — that one survives without a resume, so it may be a separate path. Filing this separately since the trigger here is isolated to session/load.
Setup: codex-acp driven over ACP by Kepler (Electron client, spawns the wrapper via
dist/index.jsand resumes stored sessions withsession/load). Reproduced on codex-acp 1.1.4 and 1.1.7, againstcodex-cli 0.144.6app-server.Problem
session/loadreturns the model and reasoning effort from~/.codex/config.tomlinstead of the ones persisted on the thread. Any in-session model/effort switch is silently lost on resume.The cause is the
modelProviderfield thatloadSession/resumeSessionalways pass tothread/resume:getResumeModelProvider()isgetCurrentModelProvider() ?? "openai", so the field is always populated even with no gateway configured. Passing it makes the app-server discard the thread's storedmodel/reasoningEffortand re-resolve them from config.Reproduction
~/.codex/config.toml. Mine hasmodel = "gpt-5.5",model_reasoning_effort = "xhigh"; the session was set togpt-5.6-sol/max."model":"gpt-5.6-sol","effort":"max"inturn_context.session/load.Observed on both versions:
Expected
gpt-5.6-sol/max(what the thread holds).Isolating it to
modelProviderCalling
thread/resumedirectly on the app-server for the same thread, varying only which params are sent:modelreasoningEffortthreadId,cwdgpt-5.6-solmaxconfiggpt-5.6-solmaxmodelProvider: "openai"gpt-5.5xhighconfig+modelProvider(what codex-acp sends)gpt-5.5xhighconfigis irrelevant;modelProvideralone flips it. The values it falls back to are exactly the ones inconfig.toml.Expected
Resuming a thread should restore the model and reasoning effort stored on that thread. Either don't send
modelProvideronthread/resumewhen no gateway is configured, or have the app-server keep the thread's model when a provider is passed. If the app-server behaviour is the intended contract, this is arguably anopenai/codexbug and I'm happy to move it there.Impact
The client cannot recover the picks on its own: after a client restart there is no live session left to read them from, and the resume response is the only source of truth. Users see the correct model for a moment (from the client's own cache), then it flips to the config default as the load response lands.
Possibly the same root cause as #336, which reports effort reverting between turns of a live session — that one survives without a resume, so it may be a separate path. Filing this separately since the trigger here is isolated to
session/load.