Summary
codex-multi-auth why-selected recomputes candidate scores without applying the account-policy layer that the runtime rotation proxy actually uses (blockedAccountIndexes / scoreBoostByAccount from lib/policy/runtime-policy.ts). As a result it can report a paused account as the winning candidate — while the runtime proxy correctly refuses to route to it. During incident response this diagnostic actively misleads: we spent time believing the router was selecting a revoked account when it was not.
Reproduction
- Have 3 accounts registered. Pause one of them:
codex-multi-auth account pause 2
- Confirm the policy is stored:
codex-multi-auth account policy list --json (shows "paused": true)
- Run
codex-multi-auth why-selected --last
- Send a real request through the runtime rotation proxy and check
~/.codex/multi-auth/app-bind/runtime-rotation-app-bind-status.json → lastAccountLabel
Expected Behavior
why-selected mirrors the runtime selection inputs: paused/drained accounts are excluded (or at least rendered as blocked (policy: paused)), and per-account policy weight boosts ((weight - 1) * 2) are included in the displayed scores, so the diagnostic agrees with what the proxy does.
Actual Behavior
The paused account is counted in "Available: 2 of 3 account(s)" and printed as Selected: with health=100, top score. Policy weight boosts are absent from the scores (an account with policy weight 10 shows no boost). Meanwhile the actual runtime proxy correctly serves requests from a different, non-paused account (lastAccountLabel confirms).
Sanitized output on 2.8.1, with account B paused (its refresh token is also revoked upstream):
why-selected: Last selection (live recomputation; no persistent tracker)
Available: 2 of 3 account(s)
Selected: account 2 <account-B>
score: 463.98
health: 100.0
...
Candidates (sorted by score desc):
* 2 <account-B>: score=463.98 health=100 ... <- paused via account policy
x 3 <account-C>: ... (unavailable (rate-limited, cooling down, or circuit open))
1 <account-A>: score=450.20 ... <- policy weight 10, no boost shown
Code pointers: lib/codex-manager/commands/why-selected.ts builds candidates from the rotation scorer only; it never calls loadRuntimePolicyState / evaluateRuntimePolicy, whereas the proxy path (lib/runtime/rotation-account-selection.ts) checks policy.blockedAccountIndexes and feeds scoreBoostByAccount into the hybrid selector.
Environment
codex --version: codex-cli 0.146.0
codex-multi-auth status output summary: 3 accounts, storage healthy, runtime rotation app-bind in use
codex-multi-auth rotation status output summary: proxy enabled, app bind running
npm ls -g codex-multi-auth: 2.8.1 (installed from a checkout of the 2.8.1 release commit; not fork-modified)
- OS: macOS (Apple Silicon)
- Node.js: 26.4.0
Diagnostic Outputs
Covered above (sanitized why-selected + account policy list). Happy to attach more on request.
Compliance Confirmation
Summary
codex-multi-auth why-selectedrecomputes candidate scores without applying the account-policy layer that the runtime rotation proxy actually uses (blockedAccountIndexes/scoreBoostByAccountfromlib/policy/runtime-policy.ts). As a result it can report a paused account as the winning candidate — while the runtime proxy correctly refuses to route to it. During incident response this diagnostic actively misleads: we spent time believing the router was selecting a revoked account when it was not.Reproduction
codex-multi-auth account pause 2codex-multi-auth account policy list --json(shows"paused": true)codex-multi-auth why-selected --last~/.codex/multi-auth/app-bind/runtime-rotation-app-bind-status.json→lastAccountLabelExpected Behavior
why-selectedmirrors the runtime selection inputs: paused/drained accounts are excluded (or at least rendered asblocked (policy: paused)), and per-account policy weight boosts ((weight - 1) * 2) are included in the displayed scores, so the diagnostic agrees with what the proxy does.Actual Behavior
The paused account is counted in "Available: 2 of 3 account(s)" and printed as
Selected:withhealth=100, top score. Policy weight boosts are absent from the scores (an account with policy weight 10 shows no boost). Meanwhile the actual runtime proxy correctly serves requests from a different, non-paused account (lastAccountLabelconfirms).Sanitized output on 2.8.1, with account B paused (its refresh token is also revoked upstream):
Code pointers:
lib/codex-manager/commands/why-selected.tsbuilds candidates from the rotation scorer only; it never callsloadRuntimePolicyState/evaluateRuntimePolicy, whereas the proxy path (lib/runtime/rotation-account-selection.ts) checkspolicy.blockedAccountIndexesand feedsscoreBoostByAccountinto the hybrid selector.Environment
codex --version: codex-cli 0.146.0codex-multi-auth statusoutput summary: 3 accounts, storage healthy, runtime rotation app-bind in usecodex-multi-auth rotation statusoutput summary: proxy enabled, app bind runningnpm ls -g codex-multi-auth: 2.8.1 (installed from a checkout of the 2.8.1 release commit; not fork-modified)Diagnostic Outputs
Covered above (sanitized
why-selected+account policy list). Happy to attach more on request.Compliance Confirmation