Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# WP3 — #4211 keep Free-tier ChatGPT accounts out of Codex pool selection

## Where this departs from the packet, and why

The packet's recorded decision was to filter in `getEligiblePoolAccounts` at `routing.ts:1248`
"rather than in `isCodexAccountUsable`". That choice is right and was kept. But the feasibility
audit that produced it did not consider a third site, and shipping only the recorded one would have
left the feature inert for the exact case the issue reports.

A read-only subagent traced it end to end. `getEligiblePoolAccounts` is the choke point for
picking a **new** account. An account that is already the active account, or already bound to a
thread by affinity, is served straight out of `isCodexAccountSelectable`, which builds its own
predicate and never consults the eligible list. Both paths return the account without the eligible
list being built at all — affinity reuse at `routing.ts:2126`, keep-active at `:2198` — and
priority preemption cannot rescue it because every account defaults to priority 0, so
`priorityOf(eligible[0]) <= priorityOf(active)` and it returns null.

The reporter's account is precisely that account: it was paid, it was taking traffic, and then the
subscription lapsed. Filtering only the eligible list would have shipped a config key that reads
correctly and changes nothing for them.

So the filter goes where pause already goes. Pause is checked in **both** places —
`isCodexAccountSelectable` at `:1014` and the `getEligiblePoolAccounts` pool-row chain at `:1248`
— and pause is the mechanism the issue itself names as today's manual workaround. Copying its two
insertion points is the smallest change that makes the policy true.

## Decisions this lane had to make

**The main account is exempt.** `getPoolAccountPlanForSelection` withholds the main plan during a
selection-only drain so routing never reads the fenced native credential for it. A rule covering
main would therefore exclude it under ordinary routing and not under drain — the same account,
two answers. Exempting it keeps the two consistent and avoids introducing a drain-time credential
read. Confirmed by audit: `isCodexAccountPlanExcluded` returns at the `__main__` check before any
plan lookup, so no new native read exists on any routing path.

**The last remaining account still serves.** Pause fails closed: all-paused returns `{status:
"none"}`. Plan exclusion deliberately does not copy that leftover. #4211 asks for "a selection
policy, not a hard block" and for an explicit route to keep working, and stranding an operator
whose remaining accounts are all excluded is a worse outcome than serving one downgraded request.
Pausing every account remains the way to stop serving entirely. Pinned by a test.

**No `minimumPlan`.** Per the packet: ranking plans needs an ordering this repository does not
have.

**Malformed policy degrades rather than failing the parse**, matching `quotaResetNotify`, because a
hand-edited typo must not trip the backup-and-defaults repair path and wipe providers or pool
accounts. The write path rejects it and `loadConfig` warns on all three success paths, so it cannot
degrade silently.

## Out of scope

`docs-site/src/content/docs/reference/configuration/providers.md` carries the field table where
`pausedCodexAccountIds` and `codexAccountPriorities` are listed, and `codexPool.excludedPlans`
belongs beside them. That file is not in the L3 owned list, so this lane documented the key in the
Codex integration guide it does own and reports the reference-table row as a follow-up.

## Audit

Four read-only `xai/grok-4.6` subagents. Two returned **fail** and both were folded in rather than
argued with.

- **Routing (fail → pass).** Four of the eight tests would have failed. Three because a `free` plan
is thirty-day-only and scores on the monthly window, while the fixture recorded weekly only, so
the account scored `CODEX_UNKNOWN_USAGE_SCORE` and lost the ranking even with no policy — the
"no policy changes nothing" tests would have passed for the wrong reason and then failed. One
because `previewCodexAccountForRequest` takes `(threadId, config)` and was called with one
argument. Both fixed; the per-row `Set` rebuild it also flagged was hoisted.
- **Config (fail → fixed).** The schema comment claimed `loadConfig` warns, and it did not:
the warning had only been wired into the diagnostics array that `ocx config show --source`
prints, not into the `warnDegraded*` helpers the proxy calls at start. Since `.catch(undefined)`
makes a malformed policy a *successful* parse, that gap meant the proxy would start, rotate onto
the accounts the operator meant to exclude, and print nothing.
- **Re-audit (pass).** All eight tests predicted to pass; both gates match pause; `__main__` exempt
before any plan read; an absent `codexPool` is a total no-op.

## Verification

Local suite, typecheck, and build: NOT RUN by operator instruction. Hosted CI on the pushed head is
the evidence.
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/fr/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,18 @@ Lorsqu'un compte quitte la sélection du pool, la raison accompagne la décision

Un renouvellement du compte principal qui n'aboutit pas répond toujours `503` avec `Retry-After`, car une nouvelle tentative peut réussir. Le message précise désormais qu'un échec persistant signifie que le compte principal doit être réauthentifié, au lieu de demander seulement de réessayer.

### Écarter de la rotation un compte rétrogradé

`codexPool.excludedPlans` liste les clés de forfait que la sélection automatique du pool ignore, comparées sans tenir compte de la casse au forfait enregistré sur chaque compte. Absent par défaut : une installation existante effectue exactement la même rotation qu'avant.

```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

C'est une politique de sélection, pas un blocage. Un compte écarté conserve ses identifiants, son historique de quota et son affinité de thread, reste visible dans la liste des comptes et demeure joignable par sélection explicite comme `work/gpt-5.4`. Seule la rotation automatique cesse de le choisir, y compris lorsqu'il est déjà le compte actif ou déjà lié à un thread — l'état exact que laisse un abonnement expiré.

Deux limites volontaires. Le compte Codex principal n'est jamais écarté par forfait, car le routage en mode sélection seule ne lit pas son forfait dans les identifiants natifs protégés ; une règle le couvrant se contredirait. Et lorsqu'il ne reste aucun compte non écarté, le compte écarté répond quand même au lieu d'échouer : mettre tous les comptes en pause reste le moyen d'arrêter complètement le service. Il n'existe pas de `minimumPlan`, car classer les forfaits ChatGPT entre eux exige un ordre total qui n'existe pas ici.

## Restauration de Codex natif

`ocx stop` arrête le proxy et le service d'arrière-plan installé, puis tente de restaurer Codex natif. OpenCodex retire les éléments de routage dont il peut vérifier la propriété et signale une restauration incomplète si les fichiers de configuration ne peuvent pas être récupérés en toute sécurité.
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,18 @@ When an account leaves pool selection, the reason travels with the decision inst

A main-account refresh that does not complete still answers `503` with `Retry-After`, because a retry may still succeed. The message now adds that a failure which persists means the main account needs reauthentication, rather than only asking for another attempt.

### Keeping a downgraded account out of rotation

`codexPool.excludedPlans` lists plan keys that automatic pool selection skips, matched case-insensitively against the plan stored on each account. It is absent by default, so an existing install rotates exactly as before.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add codexPool to the canonical configuration reference

This introduces a top-level persisted configuration section, but docs-site/src/content/docs/reference/configuration/providers.md still lists the adjacent pool fields without codexPool or excludedPlans. Users relying on the configuration reference therefore cannot discover the field's type, default, validation, or main-account exception; update that directly affected field table alongside this guide.

AGENTS.md reference: docs-site/AGENTS.md:L13-L16

Useful? React with 👍 / 👎.


```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

This is a selection policy, not a block. An excluded account keeps its credential, quota history, and thread affinity, stays visible on the account surface, and is still reachable by explicit account selection such as `work/gpt-5.4`. What changes is that automatic rotation stops choosing it, including when it is already the active account or already bound to a thread — which is the state a lapsed subscription leaves behind.

Two deliberate limits. The main Codex account is never excluded by plan, because selection-only routing withholds its plan rather than reading the fenced native credential, so a rule covering it would disagree with itself. And when no unexcluded account remains, the excluded one still answers rather than failing closed; pausing every account is still the way to stop serving entirely. There is no `minimumPlan` counterpart, because ranking ChatGPT plans against each other needs a total ordering that does not exist here.

## Restoring native Codex

`ocx stop` stops the proxy and any installed background service, then attempts to restore native Codex. OpenCodex removes verified routing artifacts and reports an incomplete restore when it cannot safely recover configuration files.
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/ja/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ ocx service install # persistent: auto-starts on login and respawns on crash

メインアカウントの更新が完了しない場合も、再試行で成功する可能性があるため `Retry-After` 付きの `503` を返します。ただしメッセージには、失敗が続くならメインアカウントの再認証が必要である旨を加えました。

### ダウングレードしたアカウントをローテーションから外す

`codexPool.excludedPlans` は、自動的なプール選択がスキップするプランキーの一覧です。各アカウントに保存されたプランと大文字小文字を区別せずに照合します。既定では未設定なので、既存の環境のローテーションは変わりません。

```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

これはブロックではなく選択ポリシーです。除外されたアカウントも資格情報・使用量履歴・スレッドアフィニティを保持し、アカウント一覧に表示され、`work/gpt-5.4` のような明示的な指定では引き続き利用できます。変わるのは自動ローテーションが選ばなくなる点で、すでにアクティブなアカウントやスレッドに紐づいている場合も含みます。サブスクリプションが失効した直後は、まさにその状態です。

意図的な制限が2つあります。メインの Codex アカウントはプランによって除外されません。選択のみのルーティングは保護されたネイティブ資格情報を読まずにプランを伏せるため、メインを対象にすると挙動が食い違うからです。また、除外されていないアカウントが1つも残らない場合は、失敗させずに除外済みのアカウントが応答します。完全に停止したい場合は従来どおり全アカウントを一時停止してください。`minimumPlan` に相当する設定はありません。ChatGPT のプランを順位付けするには、ここに存在しない全順序が必要になるためです。

## ネイティブ Codexの復元

`ocx stop` はプロキシとインストール済みのバックグラウンドサービスを停止し、ネイティブ Codex の復元を試みます。OpenCodex は所有を確認できるルーティング設定を削除し、設定ファイルを安全に復元できない場合は未完了として報告します。
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/ko/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ ChatGPT 계정을 추가하거나 재인증할 때 OpenCodex는 일반적으로

메인 계정 갱신이 끝나지 않은 경우에도 재시도로 성공할 수 있으므로 `Retry-After`와 함께 `503`을 반환합니다. 다만 실패가 계속되면 메인 계정을 다시 인증해야 한다는 내용을 메시지에 덧붙였습니다.

### 등급이 내려간 계정을 로테이션에서 빼기

`codexPool.excludedPlans`는 자동 풀 선택이 건너뛸 플랜 키 목록입니다. 각 계정에 저장된 플랜과 대소문자를 구분하지 않고 비교합니다. 기본값은 없음이므로 기존 설치의 로테이션은 그대로입니다.

```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

차단이 아니라 선택 정책입니다. 제외된 계정도 자격 증명과 사용량 기록, 스레드 어피니티를 그대로 유지하고 계정 목록에도 계속 보이며 `work/gpt-5.4` 같은 명시적 지정으로는 여전히 쓸 수 있습니다. 달라지는 것은 자동 로테이션이 그 계정을 고르지 않는다는 점이고, 이미 활성 계정이거나 스레드에 묶여 있는 경우도 포함합니다. 구독이 만료된 계정이 바로 그 상태입니다.

의도한 제한이 두 가지 있습니다. 메인 Codex 계정은 플랜으로 제외하지 않습니다. 선택 전용 라우팅은 보호된 네이티브 자격 증명을 읽지 않고 플랜을 감추기 때문에, 메인까지 적용하면 상황에 따라 판정이 어긋납니다. 그리고 제외되지 않은 계정이 하나도 남지 않으면 실패시키지 않고 제외된 계정이 그대로 응답합니다. 완전히 멈추려면 지금처럼 모든 계정을 일시 중지하면 됩니다. `minimumPlan`에 해당하는 설정은 없습니다. ChatGPT 플랜에 순위를 매기려면 여기 존재하지 않는 전순서가 필요합니다.

## 네이티브 Codex 복원

`ocx stop`은 proxy와 설치된 background service를 중지한 뒤 네이티브 Codex 복원을 시도합니다. OpenCodex 소유로 확인된 라우팅 항목을 제거하며, 설정 파일을 안전하게 복구할 수 없으면 미완료로 보고합니다.
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/ru/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,18 @@ v1/base/v2 при делегировании и fallback — в

Незавершённое обновление основного аккаунта по-прежнему отвечает `503` с `Retry-After`, потому что повтор может пройти. Теперь сообщение добавляет, что стойкий сбой означает необходимость повторной аутентификации основного аккаунта, а не только очередную попытку.

### Как убрать понижённый аккаунт из ротации

`codexPool.excludedPlans` перечисляет ключи тарифов, которые автоматический выбор пула пропускает, сравнивая их с тарифом аккаунта без учёта регистра. По умолчанию ключ отсутствует, поэтому существующая установка работает ровно как раньше.

```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

Это политика выбора, а не блокировка. Исключённый аккаунт сохраняет учётные данные, историю квот и привязку к треду, остаётся видимым в списке и по-прежнему доступен при явном выборе вроде `work/gpt-5.4`. Меняется только то, что автоматическая ротация перестаёт его выбирать — в том числе когда он уже активен или уже привязан к треду, а именно это состояние остаётся после истёкшей подписки.

Два намеренных ограничения. Основной аккаунт Codex никогда не исключается по тарифу: маршрутизация в режиме «только выбор» скрывает его тариф, чтобы не читать защищённые нативные учётные данные, и правило для него противоречило бы само себе. А если не осталось ни одного неисключённого аккаунта, исключённый всё равно отвечает вместо отказа; чтобы остановить обслуживание полностью, по-прежнему нужно поставить на паузу все аккаунты. Аналога `minimumPlan` нет: чтобы ранжировать тарифы ChatGPT, нужен полный порядок, которого здесь не существует.

## Восстановление нативного Codex

`ocx stop` останавливает прокси и установленную фоновую службу, затем пытается восстановить нативный Codex. OpenCodex удаляет настройки маршрутизации, принадлежность которых может подтвердить, и сообщает о неполном восстановлении, если файлы конфигурации нельзя безопасно восстановить.
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/tr/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ Bir hesap havuz seçiminden çıktığında neden, görüntüleme için yeniden

Tamamlanmayan bir ana hesap yenilemesi, yeniden denemede başarılı olabileceği için hâlâ `Retry-After` ile `503` yanıtı verir. Mesaj artık kalıcı bir başarısızlığın ana hesabın yeniden kimlik doğrulaması gerektirdiğini de belirtiyor.

### Sürümü düşen bir hesabı rotasyondan çıkarma

`codexPool.excludedPlans`, otomatik havuz seçiminin atladığı plan anahtarlarını listeler ve her hesapta saklanan planla büyük/küçük harf gözetmeden karşılaştırır. Varsayılan olarak yoktur; mevcut bir kurulum tam olarak eskisi gibi rotasyon yapar.

```bash
ocx config set codexPool '{"excludedPlans":["free"]}'
```

Bu bir engelleme değil, seçim politikasıdır. Dışarıda bırakılan hesap kimlik bilgisini, kota geçmişini ve iş parçacığı bağını korur, hesap listesinde görünmeye devam eder ve `work/gpt-5.4` gibi açık bir seçimle hâlâ erişilebilir. Değişen tek şey, otomatik rotasyonun onu artık seçmemesidir; hesap zaten etkin olsa ya da bir iş parçacığına bağlı olsa bile. Süresi dolan bir abonelik tam olarak bu durumu bırakır.

İki kasıtlı sınır var. Ana Codex hesabı plana göre hiçbir zaman dışarıda bırakılmaz: yalnızca-seçim yönlendirmesi korunan yerel kimlik bilgisini okumamak için planını saklar, dolayısıyla ana hesabı kapsayan bir kural kendisiyle çelişirdi. Ayrıca dışarıda bırakılmamış hiçbir hesap kalmadığında, dışarıda bırakılan hesap başarısız olmak yerine yine yanıt verir; hizmeti tamamen durdurmak için hâlâ tüm hesapları duraklatmak gerekir. `minimumPlan` karşılığı yoktur, çünkü ChatGPT planlarını sıralamak burada bulunmayan bir tam sıralama gerektirir.

## Yerel Codex'i geri yükleme

`ocx stop`, proxy'yi ve kurulu arka plan servisini durdurur, ardından yerel Codex'i geri yüklemeyi dener. OpenCodex yalnızca sahipliğini doğrulayabildiği yönlendirme öğelerini kaldırır; yapılandırma dosyaları güvenle geri yüklenemiyorsa işlemin tamamlanmadığını bildirir.
Expand Down
Loading
Loading