Skip to content

Memory writer sends hardcoded gpt-5.6-luna and gpt-5.6-terra requests to non-OpenAI model providers #37009

Open

Description

@asdf8675309

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-luna
  • gpt-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.

  1. 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"]
  1. Leave [memories] extract_model and consolidation_model unset.

  2. Do some work, exit, then start Codex again so the memory startup pass runs.

  3. Read the OpenRouter activity log. Requests appear for gpt-5.6-luna and gpt-5.6-terra alongside 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.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    custom-modelIssues related to custom model providers (including local models)
    on Aug 5, 2026
  2. github-actions commented on Aug 5, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. asdf8675309 commented on Aug 5, 2026

    @asdf8675309
    Author

    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" at
    codex-rs/memories/write/src/phase1.rs:193, and
    DEFAULT_MEMORY_CONSOLIDATION_PREFERRED_MODEL = "gpt-5.6-terra" at
    phase2.rs:359. Both are real model IDs, so a gateway serves them and nothing
    errors. 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.

  4. ded-furby commented on Aug 5, 2026

    @ded-furby

    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.

  5. metrofun commented on Aug 11, 2026

    @metrofun

    Workaround if you need to keep Codex unchanged: route the two hardcoded model names yourself.

    I verified this with swobuforge/swobu:

    gpt-5.6-luna → your chosen model
    gpt-5.6-terra → your chosen model

    Codex still emits those names, but the gateway controls what actually runs upstream.

    Install Swobu:

    curl -fsSL https://swobu.com/install.sh | sh

    Create routes for those two names and point them at the model you want.

  6. MatsukataMusubi commented on Sep 27, 2026

    @MatsukataMusubi

    Confirming this on Windows with a third-party provider, and adding the billing side — which I think is the more serious half of the bug.

    Setup

    • Codex CLI 0.155.0-alpha.9.2 (also reproduces on 0.155.0-alpha.16.3), Codex Desktop 26.915.x / 26.917.x
    • model_provider = a third-party OpenAI-compatible gateway, wire_api = "responses"
    • [memories] generate_memories = true, use_memories = true, and neither extract_model nor consolidation_model set

    Observed

    • 87 requests with model=gpt-5.6-terra sent to the third-party endpoint, cwd = ~/.codex/memories, all HTTP 200. I never selected that model id.
    • The same model id returns 400 when Codex is pointed at a different endpoint that does not serve it. So the failure is silent only because the gateway happens to accept the id — consistent with the root cause described above.
    • Also observed get_model_info{model="gpt-5.6-luna"} resolving during a thread/resume, i.e. the extraction path hits the same constants.

    The part I had not seen reported: the provider bills you under a different model name

    Querying the provider's per-model usage breakdown for the incident day:

    • gpt-5.6-terra and gpt-5.6-luna appear nowhere in the billing breakdown
    • the usage is recorded under a completely different model id (a first-party model of that vendor, ~5.88M tokens that day)
    • i.e. the endpoint accepted a model id it does not host, silently substituted another model, and billed for the substitute — no error, and nothing in the usage record tells the user which model actually ran

    So the user is not only billed for a model they did not choose; the invoice does not even show the model id that was requested. Reconciliation is impossible from the billing side alone.

    Why this is worse than "wrong model id"

    The request originates from Codex, but the silent success is created by the gateway. Any client-side bug that emits an unknown model id — this one, DEFAULT_APPROVAL_REVIEW_PREFERRED_MODEL, or a future one — will be silently billed instead of failing fast. A hard 404/400 costs nothing.

    Two asks

    1. Client side (this issue): when the active provider is not OpenAI-hosted, fall back to the active session model, or at minimum fail loudly if the provider does not advertise the id. Please cover all three constants (codex-auto-review, gpt-5.6-luna, gpt-5.6-terra).
    2. Gateway side (for the provider, and for anyone operating a compatible gateway): reject model ids that are not in the caller's plan/allowlist instead of substituting, and keep the requested model id in the usage record so the user can reconcile.

    Notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingcustom-modelIssues related to custom model providers (including local models)memory

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions