Memory writer sends hardcoded gpt-5.6-luna and gpt-5.6-terra requests to non-OpenAI model providers
What version of Codex CLI is running?
codex-cli 0.145.0
What subscription do you have?
ChatGPT Plus — not used for this session, which authed via API key through a custom model_provider.
Which model were you using?
A non-OpenAI model, selected with -m and served through a custom model_provider. The specific model does not matter — the behavior below is the same for any non-OpenAI model.
What platform is your computer?
macOS 25.5.0, arm64.
What issue are you seeing?
When Codex runs against a non-OpenAI model_provider, the memory writer ignores the active session model and sends requests using two hardcoded OpenAI model IDs. The active provider serves them, so nothing errors. The calls are only visible in the provider's activity log.
I selected a non-OpenAI model. These appeared in the provider log for the same session:
gpt-5.6-lunagpt-5.6-terra
I did not select either model. Both were billed to my account at the provider. The requests originate from my machine, but for a model I did not choose.
Root cause. All line numbers below are from main at the time of writing. codex-rs/model-provider/src/provider.rs defines three provider-blind defaults:
pub const DEFAULT_APPROVAL_REVIEW_PREFERRED_MODEL: &str = "codex-auto-review";
pub const DEFAULT_MEMORY_EXTRACTION_PREFERRED_MODEL: &str = "gpt-5.6-luna";
pub const DEFAULT_MEMORY_CONSOLIDATION_PREFERRED_MODEL: &str = "gpt-5.6-terra";
The trait methods that return them are only overridden by AmazonBedrockModelProvider. Every other provider inherits the OpenAI values.
The memory writer reads them directly. codex-rs/memories/write/src/phase1.rs:193:
let model_name = config.memories.extract_model.clone().unwrap_or_else(|| {
context
.provider()
.memory_extraction_preferred_model()
.to_string()
});
codex-rs/memories/write/src/phase2.rs:359:
config
.memories
.consolidation_model
.clone()
.unwrap_or_else(|| provider.memory_consolidation_preferred_model().to_string())
Neither falls back to the active session model when the provider is not OpenAI-hosted. The fallback is the OpenAI constant.
This is the same class as #31255 and #31732, which cover DEFAULT_APPROVAL_REVIEW_PREFERRED_MODEL. Those report a hard failure: the provider rejects the unknown ID codex-auto-review with a 400 or 404. The memory-writer variant behaves differently, because gpt-5.6-luna and gpt-5.6-terra are real IDs that many gateways serve. There is no error, so the behavior is silent.
The memory writer reads prior thread rollouts, so this sends recorded session content to models the user did not select.
What steps can reproduce the bug?
Observed through ori 0.4.0+063b32e, OpenRouter's wrapper binary that launches the Codex CLI against the OpenRouter API.
- Run
ori codex --model <any non-openai model>.
ori codex launches the installed Codex CLI with these config overrides, plus -m <model> and OPENROUTER_API_KEY in the environment:
-c model_provider=openrouter
-c model_providers.openrouter.name="OpenRouter"
-c model_providers.openrouter.base_url="https://openrouter.ai/api/v1"
-c model_providers.openrouter.wire_api="responses"
-c model_providers.openrouter.auth.command="sh"
-c model_providers.openrouter.auth.args=["-c","echo $OPENROUTER_API_KEY"]
- Leave
[memories] extract_modelandconsolidation_modelunset.
- Do some work, exit, then start Codex again so the memory startup pass runs.
- Read the OpenRouter activity log. Requests appear for
gpt-5.6-lunaandgpt-5.6-terraalongside the requests for the selected model.
Any OpenAI-compatible gateway that serves those two model IDs should behave the same way. ori is how I hit it, not a precondition.
What is the expected behavior?
When the active provider is not OpenAI-hosted, the memory writer should use the active session model — the same intent guardian_review_session_config already carries in codex-rs/core/src/guardian/review.rs, where the non-matching branch falls back to turn.model_info.slug.
If a hardcoded default must be kept, Codex should not send an unselected model to a third-party provider without disclosing it.
The same provider gate proposed for the guardian path in #31732 applies here — resolve memory_extraction_preferred_model and memory_consolidation_preferred_model against the session model unless the provider is OpenAI-hosted, and always honor an explicit extract_model / consolidation_model.
Additional information
Outline of a potential fix, per the contributing guide's request for a high-level outline rather than a PR.
The gate already exists as a field. ModelProviderInfo.requires_openai_auth (codex-rs/model-provider/src/provider.rs:71) distinguishes first-party from custom providers, and the crate already derives a stricter form of it in provider_uses_first_party_auth_path at line 255.
Applying that gate at both memory call sites keeps the OpenAI defaults for OpenAI-hosted providers and falls back to the active session model otherwise:
// phase1.rs, build_request_context
let model_name = config.memories.extract_model.clone().unwrap_or_else(|| {
let provider = context.provider();
if provider.info().requires_openai_auth {
provider.memory_extraction_preferred_model().to_string()
} else {
// active session model, the value the guardian path reads as
// turn.model_info.slug
session_model.to_string()
}
});
Phase 2 takes the same shape around memory_consolidation_preferred_model.
Three notes on this outline. An explicit extract_model or consolidation_model should keep winning, which the existing unwrap_or_else already gives. The same gate applies to the guardian path in #31255 and #31732, so one shared helper may be better than three call sites. And I have not built or tested this — the accessor for the session model inside the memories crate is the part I could not confirm from reading alone.
Workaround. Set both keys explicitly:
[memories]
extract_model = "<your model>"
consolidation_model = "<your model>"
Or disable the subsystem:
[memories]
generate_memories = false
One related note. MemoriesToml.min_rate_limit_remaining_percent is documented as "Minimum remaining percentage required in Codex rate-limit windows before memory startup runs." That gate also assumes an OpenAI backend, so the memory startup path carries more than one OpenAI assumption.
Related: #31255, #31732.
4 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Not a duplicate of #36094. That issue — along with #31255 and #31732 — is the
approval-review path, which reads
DEFAULT_APPROVAL_REVIEW_PREFERRED_MODEL = "codex-auto-review". It fails loudly,because the provider rejects an ID it does not have.
This issue is the memory writer, which reads two different constants:
DEFAULT_MEMORY_EXTRACTION_PREFERRED_MODEL = "gpt-5.6-luna"atcodex-rs/memories/write/src/phase1.rs:193, andDEFAULT_MEMORY_CONSOLIDATION_PREFERRED_MODEL = "gpt-5.6-terra"atphase2.rs:359. Both are real model IDs, so a gateway serves them and nothingerrors. The only signal is a charge in the provider's activity log.
Same root-cause shape, three separate constants, and this pair is silent rather
than fatal. One shared provider gate would cover all of them.
I agree this looks like a provider-routing bypass. The reported default constants currently point to gpt-5.6-luna and gpt-5.6-terra, which can route provider-incompatible IDs through non-OpenAI gateways. That can create silent provider-specific failures/charges instead of a hard failure. If this remains accepted, a likely fix path is to derive memory defaults from the same provider-aware model selection path used for response model resolution, with deterministic fallback and regression coverage for unsupported provider IDs.
Workaround if you need to keep Codex unchanged: route the two hardcoded model names yourself.
I verified this with [swobuforge/swobu](github.com/swobuforge/swobu):
gpt-5.6-luna→ your chosen modelgpt-5.6-terra→ your chosen modelCodex still emits those names, but the gateway controls what actually runs upstream.
Install Swobu:
Create routes for those two names and point them at the model you want.