Skip to content

Model switch is blocked when previous-model compaction hits HTTP 429 #32453

Closed

Description

@Wibias

What version of the Codex App are you using (From “About Codex” dialog)?

26.707.31428

What subscription do you have?

Pro

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What issue are you seeing?

Switching an existing Codex thread from a rate-limited model to another available model fails when the thread requires compaction before the next turn can run.

In my case, the thread previously used:

anthropic/claude-fable-5

That model's usage quota was exhausted, so I switched the thread to:

gpt-5.6-sol

Codex confirmed the model change:

Modellwechsel von Benutzerdefiniert zu GPT-5.6 Sol.

The session log also shows that the new model was applied:

thread_settings_applied:
  model: gpt-5.6-sol
  model_provider_id: openai

However, when I sent the next message, Codex failed before the newly selected model handled it:

Error running remote compact task: exceeded retry limit, last status: 429 Too Many Requests

The compact request was still sent to the previous model:

{
  "model": "claude-fable-5",
  "provider": "anthropic",
  "requestedModel": "anthropic/claude-fable-5",
  "status": 429,
  "errorCode": "rate_limit_exceeded"
}

The upstream provider returned:

This request would exceed your account's rate limit. Please try again later.

It appears that Codex attempts to compact the existing history using the previous model before continuing with the newly selected model.

Codex already has a fallback that retries compaction with the current model when the previous model returns CodexErr::InvalidRequest. However, when an HTTP 429 retry budget is exhausted, the failure is surfaced as CodexErr::RetryLimit, so the current-model fallback is not used.

As a result, the previous provider remains a hard dependency even after the user explicitly switches to another available model. The thread cannot continue until the previous provider's quota resets.

What steps can reproduce the bug?

  1. Create or open a Codex thread using model A, for example:

    anthropic/claude-fable-5
    
  2. Continue using the thread until its history is large enough that switching to a model with a smaller context window requires compaction.

  3. Exhaust model A's account quota, or otherwise make its provider consistently return HTTP 429.

  4. Switch the existing thread to an available model B from another provider, for example:

    gpt-5.6-sol
    
  5. Confirm that Codex reports the model switch and applies model B to the thread settings.

  6. Send a new message in the same thread.

  7. Codex starts a remote compaction task using model A instead of model B.

  8. The previous provider returns HTTP 429 until the retry limit is exceeded.

  9. The entire turn fails before model B receives the new user message.

Observed error:

Error running remote compact task: exceeded retry limit, last status: 429 Too Many Requests

Retrying the message produces the same result because Codex continues trying to compact with the unavailable previous model.

What is the expected behavior?

When Codex switches an existing thread to a new model and compaction with the previous model fails because that model is unavailable due to rate or quota limits, Codex should retry compaction using the currently selected model.

Expected flow:

Attempt compaction with previous model
    ↓
Previous model returns HTTP 429 and retries are exhausted
    ↓
Retry compaction with the currently selected model
    ↓
Continue the turn using the currently selected model

This should use the same fallback mechanism that already exists for CodexErr::InvalidRequest.

The fallback should only apply when:

  • Codex is compacting during a model transition.
  • A current-model fallback_step_context exists.
  • The previous model failed because it is unavailable, rate-limited, quota-limited, retired, or unsupported.

Normal requests that receive HTTP 429 should not silently switch models.

If compaction with the current model also fails, Codex should return that failure normally.

Additional information

The model switch itself appears to be applied correctly. The session records gpt-5.6-sol as the active thread model and uses its context-window configuration. However, the subsequent remote compaction request is still sent using the previous model, anthropic/claude-fable-5.

The failing request is:

Model: claude-fable-5
Provider: anthropic
Requested model: anthropic/claude-fable-5
Status: 429
Error: exceeded retry limit, last status: 429 Too Many Requests

The upstream response was:

{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "This request would exceed your account's rate limit. Please try again later."
  }
}

The likely source of the issue is the fallback condition in both remote-compaction implementations:

codex-rs/core/src/compact_remote.rs
codex-rs/core/src/compact_remote_v2.rs

The current logic appears to retry with the selected model only for CodexErr::InvalidRequest:

if !matches!(&error, CodexErr::InvalidRequest(_)) {
    return Err(error);
}

The reproduced failure is instead surfaced as:

exceeded retry limit, last status: 429 Too Many Requests

That appears to correspond to:

CodexErr::RetryLimit(RetryLimitReachedError {
    status: StatusCode::TOO_MANY_REQUESTS,
    ...
})

A possible narrowly scoped fix would be to allow fallback when the previous-model compact request exhausts its retries with HTTP 429:

fn should_fallback_to_current_model(error: &CodexErr) -> bool {
    matches!(error, CodexErr::InvalidRequest(_))
        || matches!(
            error,
            CodexErr::RetryLimit(inner)
                if inner.status == StatusCode::TOO_MANY_REQUESTS
        )
}

Both remote-compaction paths could then replace:

if !matches!(&error, CodexErr::InvalidRequest(_)) {
    return Err(error);
}

with:

if !should_fallback_to_current_model(&error) {
    return Err(error);
}

Because this fallback would still only run when a current-model fallback_step_context exists, ordinary requests receiving HTTP 429 would not silently switch models.

Suggested regression coverage:

InvalidRequest                  -> fallback
RetryLimit with HTTP 429        -> fallback
RetryLimit with HTTP 503        -> no fallback
No fallback_step_context        -> preserve original error
Current-model fallback fails    -> return fallback error normally

Additional information

The model switch itself appears to be applied correctly. The session records gpt-5.6-sol as the active thread model and uses its context-window configuration. However, the subsequent remote compaction request is still sent using the previous model, anthropic/claude-fable-5.

The failing request is:

Model: claude-fable-5
Provider: anthropic
Requested model: anthropic/claude-fable-5
Status: 429
Error: exceeded retry limit, last status: 429 Too Many Requests

The upstream response was:

{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "This request would exceed your account's rate limit. Please try again later."
  }
}

This suggests that Codex intentionally attempts compaction with the previous model during a model transition. The existing fallback to the newly selected model appears to be limited to CodexErr::InvalidRequest.

In this case, the exhausted HTTP 429 retries are surfaced as CodexErr::RetryLimit, so the fallback is skipped and the whole turn fails before gpt-5.6-sol receives the user message.

The relevant condition appears to exist in both:

codex-rs/core/src/compact_remote.rs
codex-rs/core/src/compact_remote_v2.rs

Current behavior:

if !matches!(&error, CodexErr::InvalidRequest(_)) {
    return Err(error);
}

A possible narrowly scoped fix would be to also allow fallback when the previous-model compact request exhausts its retries with HTTP 429:

fn should_fallback_to_current_model(error: &CodexErr) -> bool {
    matches!(error, CodexErr::InvalidRequest(_))
        || matches!(
            error,
            CodexErr::RetryLimit(inner)
                if inner.status == StatusCode::TOO_MANY_REQUESTS
        )
}

This fallback would still only run when a current-model fallback_step_context exists, so ordinary model requests receiving HTTP 429 would not silently switch models.

Activity

  1. added
    appIssues related to the Codex desktop app
    on Jul 11, 2026
  2. added
    bugSomething isn't working
    contextIssues related to context management (including compaction)
    rate-limitsIssues related to rate limits, quotas, and token usage reporting
    custom-modelIssues related to custom model providers (including local models)
    on Jul 11, 2026
  3. Wibias commented on Jul 12, 2026

    @Wibias
    Author

    I reproduced this issue again, this time when switching an existing long-running thread from gpt-5.6-sol to cursor/auto.

    Codex displayed the model change:

    Modellwechsel von GPT-5.6 Sol zu cursor/auto.
    

    The session log also confirms that the new thread settings were applied before the next turn started:

    thread_settings_applied:
      model: cursor/auto
      model_provider_id: openai
      reasoning_effort: none
    

    However, the automatic context compaction still requested the previous model:

    {
      "requestId": "ocx-mrhc0e3e-av",
      "model": "gpt-5.6-sol",
      "provider": "openai",
      "requestedModel": "gpt-5.6-sol",
      "requestedEffort": "high",
      "status": 429,
      "durationMs": 544,
      "errorCode": "rate_limit_exceeded",
      "closeReason": "non_stream",
      "upstreamError": "The usage limit has been reached",
      "usageStatus": "unreported"
    }

    Both model and requestedModel were already set to gpt-5.6-sol in the proxy log. This indicates that Codex selected the stale model before sending the request. cursor/auto was never attempted.

    There was also an additional retry-loop problem. After the failed compaction, Codex repeatedly added the same two messages to the conversation:

    Kontext automatisch komprimiert
    
    Du hast dein Nutzungslimit erreicht. Upgrade deinen Tarif oder füge Credits hinzu, um fortzufahren, oder versuche es am 10:31 erneut.
    

    This pair appeared seven times in immediate succession.

    The message Kontext automatisch komprimiert also appears to be incorrect because the compaction request returned HTTP 429 and did not complete successfully.

    Relevant context:

    Previous model: gpt-5.6-sol
    Selected model: cursor/auto
    Previous model context window: 353400
    Last request total tokens: 269703
    New model context window: 190000
    5-hour usage: 100%
    Weekly usage: 88%
    Credits: 0
    

    Compaction was therefore expected after switching to the smaller context window, but it should have used the newly selected model.

    This reproduction adds two details to the original report:

    1. The compaction request explicitly contained the previous model as requestedModel, despite the new thread settings already being applied.
    2. A terminal usage-limit error caused an immediate repeated retry loop and multiple incorrect compaction-success messages.
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

    appIssues related to the Codex desktop appbugSomething isn't workingcontextIssues related to context management (including compaction)custom-modelIssues related to custom model providers (including local models)rate-limitsIssues related to rate limits, quotas, and token usage reporting

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions