Custom providers can show quota warnings from the local ChatGPT account
Summary
When Codex is configured with a custom model provider whose requests use credentials/account separate from the local ChatGPT login, proactive quota notices can still be driven by the local ChatGPT account.
For example, Codex may show:
Heads up, you have less than 10% of your weekly limit left.
even though the active custom provider is routing the request through a different account with sufficient quota.
Reproduction
- Log Codex in to a ChatGPT account so that
~/.codex/auth.jsoncontains local account state. - Configure and select a custom model provider with
requires_openai_auth = false. - Have that provider send inference requests using separate credentials/account.
- Let the locally logged-in ChatGPT account approach its rate limit while the account actually selected by the provider still has sufficient quota.
- Run a model turn through the custom provider.
The TUI can show a proactive low-quota warning based on the local account rather than the account that served the turn.
Expected behavior
- Local ChatGPT account snapshots must not drive proactive quota warnings when the active provider does not require OpenAI authentication.
- Rate-limit metadata returned with the active model turn should still drive accurate warnings for the account/provider that actually served the request.
- OpenAI-specific lower-cost model-switch nudges should not be shown for custom providers.
- Correct warnings should not require an additional quota API request on every turn; Codex should use metadata already present on the successful turn response.
Root cause
The notice path treats local AccountUsage snapshots and turn-derived RollingUpdate snapshots equivalently. Their provenance matters:
AccountUsagerepresents the locally logged-in ChatGPT account.RollingUpdatecomes from the active model turn response.
For a custom provider, only the latter can describe the quota state of the provider/account that actually served the request.
Validated patch
I prepared and tested a minimal patch that scopes notices by snapshot provenance and provider authentication behavior:
- Branch: https://github.com/MeroZemory/codex/tree/agent/provider-rate-limit-notices
- Commit: https://github.com/MeroZemory/codex/commit/e2060ea87
- Compare: https://github.com/openai/codex/compare/main...MeroZemory:codex:agent/provider-rate-limit-notices?expand=1
Added tests:
custom_provider_ignores_local_account_rate_limit_noticescustom_provider_warns_from_turn_rate_limits_without_model_nudge
Verification:
- Targeted tests: 2/2 passed
- Full
codex-tui: 3,145/3,146 passed just fix -p codex-tui: passedjust fmt: passed
The one full-suite failure is an unrelated existing Windows-only pet image assertion:
pets::tests::kitty_local_file_pet_image_uses_file_reference_without_inline_payload
That test uses absence of the literal Base64 substring cG5n as a proxy for ensuring the bytes b"png" were not sent inline. However, the local-file protocol intentionally Base64-encodes the canonical file path, and a Windows path ending in frame.png can legitimately contain the same substring depending on Base64 alignment. The output still uses Kitty's file-transfer mode (t=f), and this patch does not touch the pet/image code.
PR availability
The repository currently limits opening pull requests to collaborators, so I could not open the PR directly. The compare link above is ready for a maintainer/collaborator to use.
2 Comments
Closing as not reproducible as filed.
On a closer read of the current source,
/statusand the startup rate-limit prefetch only fetch the local ChatGPT account's limits whenshould_prefetch_rate_limits()is true, i.e.config.model_provider.requires_openai_auth && has_chatgpt_account(tui/src/chatwidget/rate_limits.rs; gate applied inslash_dispatch.rsfor/statusand inapp.rsfor the startup prefetch, and covered by the teststatus_command_renders_immediately_without_rate_limit_refresh). So a session whose active provider is a custom provider withrequires_openai_auth = falsenever fetches or displays the local account's limits to begin with.The behavior I originally observed turned out to be a resumed session:
codex resumerestores the thread's savedmodel_provider(hereopenai) unless an explicit override is passed (intended behavior, #32746). The session was therefore actually running asopenaiagainst the local ChatGPT account, so the warning correctly reflected that account — not a custom provider mislabeling.Apologies for the noise. If a genuinely reachable case exists — e.g. a stale
AccountUsagesnapshot surviving a mid-session provider switch, where the/statusrows have no provenance gate — I'll open a fresh issue with a concrete reproduction and, if warranted, a narrowly scoped patch (the OpenAI-specific model-switch nudge suppression for custom providers was the only part of my draft with independent value).Reopening with a corrected analysis. My earlier close was overstated: "a custom-provider session never fetches or displays the local account's limits" holds only for a fresh custom-provider launch, not after an in-process transition from an OpenAI-authenticated context.
Why the originally-filed (pure custom-provider) repro does not reproduce
account_state()reads the auth manager only whenrequires_openai_auth, returningNoneotherwise (model-provider/src/provider.rs:285). So on a fresh launch with a custom provider (requires_openai_auth = false),has_chatgpt_account/has_codex_backend_authare both false, and/status, the startup prefetch, and/usageall skip the local-account fetch (/status+ startup gate onshould_prefetch_rate_limits = requires_openai_auth && has_chatgpt_account;/usagegates onhas_codex_backend_auth).The actually-reachable path (provider transition)
When a thread is forked/resumed in-process, the new widget is built with the new config but copies the previous widget's account flags (
tui/src/app.rs:739-747:has_chatgpt_account: self.chat_widget.has_chatgpt_account(),has_codex_backend_auth: self.chat_widget.has_codex_backend_auth()). After switching OpenAI → custom provider and forking/resuming without restarting, those flags stay stale-true. Then/usage(gated onhas_codex_backend_auth) and the reset-credit flow (OpenRateLimitResetCredits, ungated —tui/src/app/event_dispatch.rs:981) callfetch_account_rate_limits, which reads the local ChatGPT account regardless of the active provider (app-server/src/request_processors/account_processor.rs). The resultingAccountUsagesnapshot, pre-patch, firedshould_warn_about_rate_limit_usage = is_codex_limit && !has_workspace_credits— a local-account warning under a custom active provider.Corrected reproduction
requires_openai_auth = false./usage-> "Redeem usage limit reset".Fix outline
RollingUpdate || requires_openai_auth, suppressing the misleading warning (the reported symptom).app.rs:739-747). Recomputinghas_chatgpt_account/has_codex_backend_authfrom the new provider on fork/resume (instead of copying the previous widget's) would also keep/statusand/usageconsistent, not just the warning.Filing as analysis-only per CONTRIBUTING (no unsolicited PR). Happy to expand the reproduction or root-cause detail.