Codex desktop: switching an existing thread to a custom API provider can break continuity and remote compact still hits api.openai.com

Open 💬 2 comments Opened Aug 17, 2026 by wenronghui123-bot
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

When using the Codex desktop app, switching authentication/model routing from ChatGPT-plan auth to a custom third-party Responses-compatible API provider is currently very fragile for long-running threads.

The existing thread can remain visible, but when the thread needs compaction or certain follow-up work, Codex may still send the request to https://api.openai.com/v1/responses instead of the configured custom provider base_url. Because the active key belongs to the third-party provider, the request fails with 401 invalid_api_key.

This creates a serious continuity/UX problem: users can have a long-running task half completed on disk, switch to a third-party provider because their ChatGPT/Codex plan quota is exhausted, and then discover that the same thread cannot continue reliably. The practical workaround becomes opening a new thread, manually recovering Git/worktree state, creating handoff files, and later re-synchronizing the old thread when ChatGPT quota returns. That is error-prone and can cause duplicate work or conflicting edits.

Expected behavior

One of these behaviors would be much safer:

  1. Provider routing should be thread-consistent: once a custom provider is active, all model-dependent subrequests for that thread (including remote/local compaction) should honor the configured provider/base URL; or
  2. If an existing thread cannot safely switch providers, Codex should explicitly block the switch and offer a first-class Continue with another provider flow that carries forward the thread summary, working directory, branch/worktree, diff state, approvals, and pending tasks; or
  3. Codex should automatically generate a durable handoff/checkpoint before switching auth/provider, then let the new provider resume from that checkpoint without requiring users to manually inspect Git/worktrees.

Reproduction

  1. Start a long-running Codex/Work task using ChatGPT-plan auth.
  2. Let the task modify a local repository/worktree and continue until the thread is long enough to require compaction.
  3. Exhaust or otherwise stop using ChatGPT/Codex plan quota.
  4. Configure a custom third-party Responses-compatible provider with its own API key and base_url.
  5. Return to the existing thread and attempt to continue.
  6. The thread may remain visible, but compaction/follow-up can fail with a request to https://api.openai.com/v1/responses and 401 invalid_api_key, instead of using the custom provider endpoint.

Why this matters

  • Long-running tasks can be stranded halfway through even though the repository state is still present locally.
  • Users are forced to manually manage config.toml, auth, Git branches/worktrees, local history, and handoff files just to change billing/provider.
  • Returning later to ChatGPT-plan auth risks stale-thread context causing repeated work or edits that conflict with changes made by the third-party-provider thread.
  • A third-party API key being routed to the wrong host is also an undesirable credential-routing failure mode, even if the request is rejected.

Requested product behavior

Please make provider switching a first-class, resumable operation at the thread/task level, with automatic preservation of:

  • thread/task summary and pending checklist
  • current working directory
  • branch/worktree identity
  • uncommitted diff state
  • approvals/permissions state
  • model/provider routing for all subrequests, including compaction

The user should not need to understand Git worktrees or manually create a handoff document simply because they switched from ChatGPT-plan usage to a custom API provider and later back again.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 11 days ago

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

  • #38365

Powered by Codex Action

jdcodes1 · 11 days ago

The "existing thread keeps hitting api.openai.com after a provider switch" half of this has a specific, verifiable mechanism in the shared app-server code (main @ 1f41cc5d92): thread resume pins the provider recorded when the thread was created, and a config-level provider switch never retargets it.

1. Resume pins the persisted provider. When a thread is loaded/resumed, its persisted metadata is merged into the effective config overrides:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/app-server/src/request_processors/thread_processor.rs#L166-L184

typesafe_overrides.model_provider = Some(persisted_metadata.model_provider.clone()) — unconditionally, unless the resume request itself carries an explicit model/provider override (has_model_resume_override, #L266-L275). So a thread created under ChatGPT-plan auth stays routed to the built-in openai provider no matter what config.toml now says. New threads read the new config and work — exactly your observed split. It also explains the inconsistency: whether a given follow-up honors the new provider depends on whether the code path that resumed the thread passed an explicit model override (which suppresses the pinning) or not.

2. Why it's compaction that fails loudest. Remote vs. local compaction is chosen from the thread's provider capabilities:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/core/src/session/turn.rs#L1183-L1236

With the pinned openai provider, remote_compaction is V1/V2, so compaction posts to the OpenAI /responses compact endpoint with whatever credentials are now active — your third-party key — producing 401 invalid_api_key. Had the thread actually been routed to the custom provider, the capability check would have selected local compaction and this failure wouldn't exist. So the compaction 401 is downstream of the pinned provider, not a separate compaction bug.

3. The credential-routing smell you flagged is real and worth prioritizing. The current shape sends the active (third-party) API key to api.openai.com because provider identity and credentials are resolved independently: the provider comes from thread metadata, the key from current auth state. A cheap invariant — before issuing any request, assert the resolved credentials belong to the resolved provider, otherwise fail with "thread is pinned to provider openai but active credentials are for <provider>" — would convert both the silent mis-routing and the confusing 401 into an actionable message.

Fix outline matching your requested behaviors:

  1. Make provider part of thread/settings/update as a first-class switch that rewrites the persisted thread metadata, so subsequent resumes pin the new provider (your option 1). The compaction path then follows automatically via the capability check in (2).
  2. Until then, resume could demote the persisted provider from "override" to "default": if the persisted provider is no longer configured/authenticated and the active config names a different provider, prefer the active one (or surface the explicit conflict error from (3) instead of a raw 401).
  3. The capability check already gives the safety property you want for compaction — thread-consistent routing makes remote compact impossible on providers that don't support it, with local compaction as the automatic fallback.

A regression test shape: create a thread under provider A with remote-compaction support, persist, switch config to provider B (no remote compaction), resume without explicit model override, trigger auto-compact — assert the request targets provider B's base URL (or the explicit conflict error), never provider A's.