Skip to content

fix: preserve Codex provider across API fallback - #8507

Merged
huangruiteng merged 3 commits into
mainfrom
codex/provider-session-stability-20261110
Nov 11, 2026
Merged

huangruiteng merged 3 commits into
mainfrom
codex/provider-session-stability-20261110

Conversation

@loopx-agent

Copy link
Copy Markdown
Collaborator

Summary

  • Keep subscription and API-fallback routes on Codex's built-in openai provider ID.
  • Route API fallback through the supported openai_base_url override instead of a custom provider whose display name is also OpenAI.
  • Migrate legacy API slots from custom provider IDs when selected.
  • Preserve existing Codex sessions across subscription/API transitions and bump the provider to 0.15.7.

Root cause

Codex binds each conversation to the internal model_provider ID, not its display name. Subscription sessions recorded openai, while API fallback switched to openai_retry20. Both displayed as OpenAI, so the UI reported a provider mismatch even though the labels looked identical.

Official documentation recommends openai_base_url when only changing the built-in OpenAI provider endpoint: https://developers.openai.com/codex/config-file/config-advanced#custom-model-providers

Validation

  • uv run --python 3.12 --with pytest pytest tests/presentation/test_native_account_store.py tests/presentation/test_openai_fallback.py tests/presentation/test_openai_relay.py packages/loopx-codex-provider-routing/tests/test_api_stream_outcome.py -q
  • Result: 73 tests and 28 subtests passed.
  • Installed 0.15.7 locally, migrated the active API slot, restarted services, and reopened the App.
  • Readback now shows model_provider=openai, relay endpoint in openai_base_url, and zero provider-mismatch events after migration.

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

审查 head:5c8c2caef8c550efcbacf21aca375a97e202f1aa

[P2] 切换账号时保留显式自定义 provider。 切到订阅账号时,Accounts.switch 就会把退出的 API 槽改写成 openai。这会丢失已保存的显式网关选择。用同一组隔离 fixture 走真实账户切换和 codex exec resume:主干恢复同一会话并请求原网关;当前 head 在发送请求前报 modelProviderMismatch。请把迁移限制在明确属于受管旧 fallback 的槽,保留显式自定义槽的写回、恢复和模型目录。

动机

在 Codex 原生账号间切换的用户,需要保留已经绑定到会话的实际 provider,继续原对话,并将请求送到为当前账号选定的入口。旧版在已登记的自定义 API 槽切走和切回时保留网关。新版在切走时已把备份槽的 provider 写成 openai;切回后,之前自定义 provider 的会话无法恢复。相同的隔离 fixture 用真实账户事务和原生 Codex CLI 在主干成功恢复同一会话并命中原网关;当前 head 在发出任何请求前就以 modelProviderMismatch 失败。不更改已安装的 provider、真实账户、配额或外部模型流量;只验证受管旧 fallback 与显式自定义槽的区别。交付前须把内置路由修正限定在可证明为受管旧 fallback 的槽,同时保留显式自定义网关及其原会话。

改动思路

复用原有账号事务、锁与回滚;修正受管旧 fallback 的 provider 映射即可。把所有 API 槽都归一化会重写用户选择,并且让已有对话无法恢复。当前 PR 的合理边界是让原本绑定内置 provider 的会话继续走受管 fallback;显式的自定义账号槽仍按其绑定的 provider 切换。 对照已接受的 packages/loopx-codex-provider-routing/NATIVE.md(744f8db4cb95b17f3736f99a7e60a995b19b6ecb):selection restores that provider 对显式自定义槽不再满足;built-in openai 对受管旧 fallback 的路由目标已有实证;readback and rollback 由既有事务保留,相关测试通过。NATIVE 文档新增迁移说明时也需消除和原约定的冲突。

具体改动

  • builtin_api_auth 按 API 类型做归一化,没有区分已绑定的显式网关和旧 fallback。Accounts.switch 同时归一化目标与退出槽,事务会保存改写后的退出槽。file_config 再把恢复路径设置成内置 provider 与 endpoint override。测试也改成接受原来显式槽被归一化的行为。
  • 独立执行了匹配的两条路线:受管旧 fallback 在当前 head 用内置 openai 和 endpoint override;显式自定义槽在切换之前两端均能请求原网关,切换后仅主干保持原 provider 并恢复同一会话。当前 head 73 个相关测试及 28 个子测试通过,标准预合并 5 项直接检查通过,但新增期望未覆盖已绑定会话的恢复。

对主干的风险

已验证的影响是显式自定义 API 槽的 provider 被持久化改写,造成原会话无法继续;只看显示名称、配置可解析或账户事务成功都发现不了。实际主干合并演练没有冲突,但该行为回归仍阻止批准。请补齐“订阅会话继续使用受管 fallback”和“自定义会话切走再切回仍命中原网关”两条独立回归,并检查切走后的槽 readback。

我的整体评价

保留内置会话的修复方向,收窄迁移边界后再审。该差异未修改 App、Lark 或渲染层;核验的是切换账号后,在真实 Codex CLI 恢复原会话的路径。未测安装态扩展、真实登录、App UI 或付费流量;原生 CLI 仅连接两个隔离 loopback 服务。

English verdict: REQUEST_CHANGES — preserve explicitly bound custom API-slot providers across account switching and keep built-in normalization scoped to managed legacy fallback.

Keep subscription and managed relay routes on the built-in openai provider identity via openai_base_url, migrating legacy loopback relay provider IDs when selected, while preserving and restoring explicit custom Responses gateway providers across account switching.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
@loopx-agent
loopx-agent force-pushed the codex/provider-session-stability-20261110 branch from 5c8c2ca to c37894c Compare November 10, 2026 19:27

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

审查 head c37894c,主干基线 d3ac3bd。

[P2] 保留共用中转地址的显式 provider 绑定。 主干恢复同一个会话,回环收到带自定义头和参数的请求。当前提交切走时把保存槽改为 openai,切回后 CLI 退出 1 并报告 modelProviderMismatch,回环收到零次恢复请求。 请按可验证的槽来源判定受管旧中转,保留显式绑定,并补上同地址下切走再切回的原生恢复测试。 变更行

动机 / Motivation

使用显式 Responses 网关的 Codex 用户,在订阅和 API 槽之间切换时需要继续原会话。 网关和受管中转共用一个本地地址时,主干能切回原 provider 并恢复会话。本次提交在切走时改写了保存的 provider,切回后 Codex 在发送请求前拒绝恢复。 已验证内置 openai 会话经过受管旧中转迁移后仍能继续。显式自定义网关需要保留已保存的 provider 绑定。 复验覆盖本地账号事务和 Codex CLI,使用合成账号与回环 Responses 服务。App UI、已安装扩展和真实登录仍待验证。

改动思路 / Approach

账号槽的配置读取、写回和回滚由原生账号事务负责。端点地址相等无法确定槽的来源和用户选择。 本 PR 应修复内置会话经过受管旧中转时的恢复,并保留已有和新建显式槽的 provider 绑定。 可以保留原账号锁和原子事务,在同一处收窄迁移判定。Codex 支持为内置 provider 设置地址覆盖,显式 provider 还能携带自己的认证、请求头和查询参数。本轮用同一套合成槽对照两版写回结果,并经原生 CLI 恢复原会话。Codex 配置文档

具体改动 / Changes

以变更前的 packages/loopx-codex-provider-routing/NATIVE.md(d3ac3bd8d69821ae5843b7cef5fdb6dcbc7cae74)为规范。selection restores that provider 在共用地址的场景失败。built-in openai 通过原生恢复对照。default-off permission 仍由原回退策略和两边的测试覆盖。readback and rollback 的账号锁、回读和回滚测试通过。变更前规范

关键代码讲解

  • managed_relay_provider 和 routed_api_auth 决定哪些槽改写为内置 openai。地址相等分支把已经明确选择的自定义 ID 当成受管旧中转。回退请求原来使用的查询参数和头也绑定在这个自定义 ID 上。分类与写回
  • file_config 生成和验证 TOML。Accounts.enroll_api 创建未选中槽时应用同一个分类,Accounts.switch 还会用它写回退出的 API 槽。新槽对照中,主干保存自定义 ID,当前提交在同地址时保存 openai,当前登录文件逐字节保持。配置转换 账号切换
  • 原生文档补了中英文迁移说明,三处版本升至 0.15.7。构建出的 wheel 已读回原生 manifest 和发行包版本。新增账号测试覆盖了旧受管槽与地址不同的自定义网关,本轮也对两种情况做了独立 CLI 恢复验证。

对主干的风险 / Risks

各组用原生 Codex CLI 创建会话并读取恢复时的退出码和真实回环请求。

场景 主干 当前提交
旧受管中转,原会话绑定内置 provider provider 不匹配,零请求 恢复同一会话,一次请求
显式网关与中转地址不同 恢复同一会话,保留自定义头 恢复同一会话,保留自定义头
显式网关与中转地址相同 恢复同一会话,保留自定义头 退出 1,provider 不匹配,零请求

主干 73 项测试与 28 个子测试通过。当前提交 75 项与 30 个子测试通过,Ruff 和 quick premerge 的 5 项直接检查通过。后续回归应同时断言槽回读、真实恢复以及网关收到的请求。

语义与 CI 对齐

这项改动沿用原生槽的 provider 字段。按地址判定迁移会破坏已接受的显式槽恢复约定。修复后请复跑账号存储测试,并通过 codex exec resume --json 复验共用地址的自定义会话与回环请求。远端 CI 状态未读取。

我的整体评价 / Verdict

内置会话走通解决了原始故障。共用地址的显式槽仍会丢失绑定,已有会话无法继续。把迁移限制在来源可确认的受管槽后,应同时复验三种恢复场景和新槽登记。产品 UI 与真实账号登录仍待验证。

English verdict: REQUEST_CHANGES — the managed legacy route resumes, but an explicit gateway sharing its endpoint is rewritten and the existing Codex session fails to resume.

@songoow songoow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported

评审 head:c37894c35a3bd5abfb8276f1ae7ccf61ea55b242;当前主干:beff0f3e55d1f70eceebbd8e78c11f1cff34643b。

动机 / Motivation

使用 Codex 订阅与 API 自动接续、并选择过自定义 gateway 的用户会遇到这个边界。

原生会话已经绑定 explicit_gateway;往返切换订阅与 API 时,PR 希望修复历史受管 relay 的 provider 名称,但不能改掉用户明确选择的 gateway。

期望旧受管 relay 能使用内置 openai,同时显式 gateway 在保存槽位与再次选择后保持原 provider。

本次范围是已有账号事务中的 provider 路由迁移,不包括重放会话、改变模型、增添 API 权限或重建账号存储。

改动思路 / Approach

沿用 Accounts 原有原子事务和路由 owner 是合适的;修复应收紧受管身份判定,地址相等不足以授权重命名。 当前 PR 边界是 provider 绑定兼容修复,不需要增加新的会话复制、授权或账号状态层。

入口仍由原生账号 owner 读取当前 config 与私有槽位,在已有 idle、revision 和事务边界内保存当前路由、选择下一个账号并写回配置。保留这些边界正确;需要区分“这是同一个传输地址”和“这个 provider 由我们管理”两件事。

具体改动 / Changes

关键代码讲解

managed_relay_provider(packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_account_store.py:211)识别旧名字及同地址 provider;routed_api_auth 和 file_config 把该分类用于入槽、出槽和最终配置。Accounts.enroll_api/switch 均有真实调用,因此错误会写入持久槽位。其余文件更新双语说明、版本到 0.15.7 及对应回归,未引入第二个 writer。

接受依据是主干 NATIVE.md,spec_revision=beff0f3e55d1f70eceebbd8e78c11f1cff34643b,criterion_id=existing_boundary:显式选择的顶层 gateway 在再次选择 API 槽位时恢复原 provider 与模型目录。当前 head 未满足。旧的无差别迁移已收窄;本轮检查完整差异后,仍确认现有 P2,不重复创建相同 inline。

对主干的风险 / Risks

[P2] URL 相等仍会吞掉显式 provider 身份。 独立运行真实 Accounts 文件事务:先使用 explicit_gateway 激活 API,再将该 provider 的 base_url 设为本地 relay endpoint,切换到订阅再切回 API。主干的 outgoing slot 与最终 config 都保留 explicit_gateway;当前 head 两处都变成 openai。仅把 URL 改为不同地址,两版都保留 explicit_gateway,确认触发条件是新增 URL 分类,不是测试槽位或其他改动。

这个错误不一定让下一次 HTTP 请求立刻失败,但会改变已有 native conversation 依赖的 provider 绑定。按受管来源或明确的旧 provider 身份迁移;不要用相同 base_url 推断可重命名身份,并补充显式 gateway 共用 relay URL 的往返切换回归。

本轮相关 Python 测试为 74 passed、1 skipped、28 subtests passed;另两个 quoted/backslash gateway 子用例在 Linux 的未修改 Host.switch 中报 platform_unavailable,主干同用例复现相同失败,不归因给本 PR。原生 macOS Codex 恢复未在本轮重新运行;这里的反例证据到实际槽位与 config 独立读回为止。远端 CI 未获取完整 rollup,不写成通过。

语义与 CI 对齐

迁移规则应以明确受管身份为依据。现有测试通过不能替代“同地址、不同 provider 身份”这一持久化边界。

我的整体评价 / Verdict

建议先修复这一已独立复现的 P2。方向和改动规模适度,无需重构账号系统;长期会话接续要求保存用户选定身份,不能让一次成功的账号切换悄悄改名。本次检查的是 CLI、文件事务或文档语义,没有修改渲染页面。完整安装后的宿主行为未在本轮重新验收。

English verdict: REQUEST_CHANGES. The URL-only migration still rewrites an explicit provider identity; preserve the selected binding before approval.

…pair-8507-20261111

Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

评审提交 32c850592c13b68aaf3e717f023501d96fd83906,主干对照 38a3223b5bd204f6f90ac95930b0e42637e018f3。

动机 / Motivation

使用 Codex 订阅、受管 API 接续或显式 Responses 网关的用户,切换账号后需要继续原来的会话。

原提交将共用中转地址的显式网关槽改写为内置 openai。隔离复验中,旧提交恢复后的请求丢失网关自定义头和查询参数。修订后,网关槽保持原 provider,受管旧中转槽迁移到内置 openai。

用原生 Codex CLI 恢复同一会话时,实际本地请求保留网关头、查询参数和前序对话。已知旧中转槽仍通过内置 provider 继续原会话。

验收覆盖本地账号文件事务、原生 CLI 与回环 Responses 服务。App 界面、真实登录和上游模型流量留在本轮验证范围之外。

改动思路 / Approach

已知中转 ID 的兼容转换放在原生账号存储,入槽、出槽和配置回读复用同一判定。付费准入和真实账号事务继续由已有 owner 管理。

本 PR 交付现有槽位的 provider 绑定兼容、0.15.7 扩展打包、双语操作说明和恢复回归。 保留已有锁、当前登录检查和事务回滚即可覆盖写入和恢复。Codex 的内置 provider 支持 endpoint override,显式 gateway 仍从自己的定义读取认证、头和请求参数。 Codex 配置文档

具体改动 / Changes

关键代码讲解

  • normalize_relay_provider 只按已知旧中转 ID 归一。新登记的 Accounts.enroll_api 与已存槽的 routed_api_auth 复用这个判定,显式网关共用 URL 时仍保留自己的 ID。 归一化与出入槽 · 登记
  • file_config 生成、解析和校验选择后的配置。Accounts.switch 锁内重读同一个槽,用既有事务保存当前与目标身份。订阅返回时移除受管 endpoint。三个版本位置与构建出的 0.15.7 wheel 一致。 配置 · 切换
  • 新增共享地址的原生回归,断言恢复同一 thread、保留首轮输入,以及回环实际收到带网关查询参数的请求。复验另读回了请求头。 真实 CLI 回归

验收依据 packages/loopx-codex-provider-routing/NATIVE.md#openai-api-fallback @ 38a3223b5bd204f6f90ac95930b0e42637e018f3。ROUTE-EXPLICIT 通过,同地址网关恢复绑定和模型目录。ROUTE-FALLBACK 通过,已知旧 ID 选择内置 provider 并保持会话。ROUTE-LOCK 通过,原事务回读和失败回滚有效。ROUTE-DEFAULT-OFF 通过,保存 Key 不会启动 relay 或授权付费切换。 变更前 NATIVE 合同

对主干的风险 / Risks

在同一 Codex CLI 0.159.2 与本地 Responses fixture 中,主干及最终提交的显式网关都带原头和参数。原 PR 的恢复请求缺少二者。最终提交的已知旧中转也继续原内置会话,订阅返回读回无中转地址。复核了旧评审对显式网关的两处记录。 原阻塞

PASS · 108 项测试与 30 个子测试、Ruff、5 项预合并检查及 wheel 读回。6 个独立 opt-in App Server SDK 组合跳过。新提交远端工作流尚在排队,当前没有失败。原提交的 CI 失败来自更早的运行,保留其历史观测。 账号回归

语义与 CI 对齐

继续使用原生 slot 的 provider 字段及现有付费开关。两个槽能用同一 URL,授权与身份仍由已验证的账号和所选 provider 决定。用新登记、已有槽、往返恢复和默认关闭回归核对这一边界。

我的整体评价 / Verdict

同意合入。长程会话能保留原历史,操作员切回同地址网关时会继续使用原路由。原生复验使用 Codex CLI 0.159.2、隔离目录、合成账号与回环 Responses。候选验证覆盖 provider 绑定、当前 config 和真实恢复请求。

English verdict: APPROVE 32c850592c13b68aaf3e717f023501d96fd83906. Native Codex resumed the same thread and preserved the explicit gateway headers and query parameters. Known legacy relay slots still select the built-in provider.

@huangruiteng
huangruiteng dismissed songoow’s stale review November 11, 2026 09:33

Verified on 32c8505. Shared loopback URLs preserve explicit provider bindings during enrollment and the full account round trip. Native Codex resumed the same thread with the original custom header and query parameter. Known legacy relay IDs still select the built-in provider. All relevant tests and local premerge checks passed.

@huangruiteng
huangruiteng merged commit de684cd into main Nov 11, 2026
6 of 9 checks passed
@huangruiteng
huangruiteng deleted the codex/provider-session-stability-20261110 branch November 11, 2026 09:34
@loopx-agent

Copy link
Copy Markdown
Collaborator Author

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

本次评审对应提交 32c850592c13b68aaf3e717f023501d96fd83906。

合并后审计 · [P2] 保留与旧中转同名的显式网关。 已合入 de684cd。显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。 用可核验的受管槽来源或明确迁移绑定判断;来源不明时保留已显式保存的 provider,补同名异端点的原生往返测试。 触发代码

动机 / Motivation

使用 Codex 订阅、受管 fallback 或显式 Responses 网关的用户需要继续原会话。内置订阅会话进入历史受管 API 槽会因 provider 名称不同而拒绝恢复;已保存的显式网关仍按绑定切回。真正的旧受管中转在本次提交保留内置 openai 会话,实际请求抵达中转。不同 ID 的显式网关即使共用地址,也恢复同一会话、请求头、查询参数与原输入。显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。本次使用原生 Codex 0.167.0-alpha.3.2、合成账号和两个回环 Responses 目标。未使用真实账号、模型流量或已安装扩展。

改动思路 / Approach

继续沿用原生账号事务、权限与配置回读,把旧中转迁移限定在有来源证据的槽。内置会话修复有效;同名显式网关仍需保住原绑定。 既有账号锁内决定路由并原子写回;Codex 根据保存的 provider ID 恢复会话,仍由既有付费策略决定是否启用 fallback。

具体改动 / Changes

关键代码讲解

原验收依据 packages/loopx-codex-provider-routing/NATIVE.md#openai-api-fallback @ 38a3223;ROUTE-EXPLICIT 未通过,ROUTE-FALLBACK、ROUTE-DEFAULT-OFF、ROUTE-LOCK 通过。合入前规范。六个路径中,归一化与槽登记 把名字集合用在已存槽、出槽和新增槽,配置回读与事务 再据此写入真实配置。其余路径补充双语迁移说明、三个 0.15.7 版本位置与同地址的原生回归。上一轮同地址问题已修好;本次反例的地址本来就不同,名字在合入前可作为普通显式网关使用。

对主干的风险 / Risks

显式网关碰巧名为 openai_retry20 时,主干对照保留该名称并恢复原会话;合入版在登记和往返切换时把槽改为 openai。恢复在发请求前报 modelProviderMismatch;新会话仍能启动,却改发受管中转且没有网关请求头。 用可核验的受管槽来源或明确迁移绑定判断;来源不明时保留已显式保存的 provider,补同名异端点的原生往返测试。 对照版的原会话与前序输入都保留。合入树与本地用同一 head 生成的合并树完全一致。

语义与 CI 对齐

真实 Codex 回环验证同时跑了受管旧中转、同地址显式控制、不同地址显式控制与同名负例。76 项相关测试及 30 个子测试、Ruff、wheel 构建和 5 项 quick premerge 直接检查通过;未读取托管 CI。

我的整体评价 / Verdict

建议在原有账号 owner 内补修显式同名槽,再用同一对端点和同一会话往返复验。现有账号切换入口;未修改渲染或首屏。真实登录、已安装状态及平台外推未验证。

English verdict: REQUEST_CHANGES. Reproduced a regression on merged head 32c8505: a named but explicit gateway is rewritten, blocking native resume and rerouting a new request.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants