v2.12.0
Effort suffixes now resolve to one key per model, usage is priced by the service tier it was actually billed at instead of assuming standard rates, and a class of lookup defect where a prototype member name was mistaken for a model id is closed. Nothing to migrate, install and go.
npm i -g codex-multi-authWhat was wrong
An effort suffix split a model into two keys.
Four call sites stripped none|minimal|low|medium|high|xhigh from a model id and stopped there. That set predates GPT-5.6, which introduced max and ultra, so gpt-5.6-sol-max and gpt-6-astra-ultra kept their suffix and keyed separately from the model they route to:
| Site | What the split key did |
|---|---|
lib/entitlement-cache.ts |
Blocked the same account and model pair twice, under two keys |
lib/capability-policy.ts |
Recorded success and failure under a key the matrix never reads back |
lib/request/error-classification.ts |
Found no fallback row, so --model gpt-6-astra-max had no fallback while gpt-6-astra did |
lib/config.ts |
Normalised a user's chain override to a key the resolver never asks for |
The set was never widened for a real reason, not an oversight: codex-max and gpt-5.1-codex-max are model names whose final segment is max. Stripping theirs renames them to codex and gpt-5.1-codex, which retargets the fallback chain and splits their cache keys instead.
A Fast session was priced at half its cost.
Every rate in MODEL_PRICING is a standard-tier rate and the estimator had no tier input at all. OpenAI's Fast tier costs more, $20/$100 against $10/$50 for GPT-6 Astra, so a session billed at it was recorded at half what it cost and a maxCostUsd cap could overrun without ever tripping.
Astra billed cached tokens at the full input rate.
2.11.0 shipped gpt-6-astra with no cachedInputUsdPerMillion, on the reasoning that no Astra-specific cached figure was published. That made it the only row in the table charging cached input at the full rate. Over-stating is the safer direction than under-stating, but a tenfold over-statement on a cache-heavy session trips a cost cap far too early and blocks legitimate work.
What's fixed
One key per model. stripModelEffortSuffix in lib/constants.ts derives its suffix set from REASONING_EFFORTS, so a tier added to the union cannot be missed here again, and it names the codex-max exception explicitly. That file was chosen because it has zero imports, so all four consumers can depend on it without a cycle.
Pricing follows the tier. service_tier is read off the Responses payload, both the bare response object and the stream-event shape that wraps it one level down, and normalised: default means standard, fast means priority (the upstream catalog names the tier priority and displays it as "Fast", so either spelling can arrive), and anything unrecognised becomes unknown rather than being dropped, since dropping it would silently restore the standard-rate under-count.
It rides on UsageTokenCounts rather than a new parameter, so it reaches the ledger through five existing call sites unchanged.
A tier with no published rate is unknown, not approximated. Astra's Fast multiplier is the only one OpenAI has published for anything in this table. Applying a blanket 2x to the rest would move a budget's trip point on a guess, so an unlisted tier reports unknown cost and evaluateBudgetGuard fails closed, exactly as it already does for an unpriced model. That includes priority on gpt-5.6-sol.
Astra's cached rate is the platform 90% discount, $1 standard and $2 on Fast, which all nine other rows already encode at exactly input/10. It is convention rather than a per-model guess, and cached input is still never billed at zero, which is the failure that made cost caps unenforceable once before.
Under the hood
Three defects in this release were found by reviewers or a release-gate stress probe rather than by the change that introduced them, and all three share a shape worth naming: a value that reaches a boundary is not the same as a value that crosses it.
createRuntimeUsageRecorderrebuilds the ledger input field by field, so the tier arrived on its input through a spread and was discarded beforeappend. Every Fast response through the runtime proxy, the default path, would still have priced at the standard rate. The test that existed covered the deferral hop, which passes whether or not the recorder keeps the field.normalizeParsedUsageRowrebuilds only the five numeric token fields, so a persisted Fast row read back as standard inusagereports.normalizeUsageLedgerRowvalidates every other field against its allowed set but accepted any truthy tier, leaving the write path as the only unvalidated way onto a row while the read path already rejected the same value.
Each now has a regression that drives the real path rather than the hop before it.
Upgrade notes
No migration is required. Install the package and confirm the version:
npm i -g codex-multi-auth
codex-multi-auth --versionThe command should report 2.12.0.
Model routing is unchanged: DEFAULT_MODEL stays gpt-5.5 and diagnostic probes still lead with gpt-5.6-sol. Existing ledger rows are unaffected, and a row with no recorded tier prices exactly as it did before, since an absent tier and standard both resolve to the base rate.
One limit worth knowing: nothing in this package selects a service tier. fastSession is a local latency setting that lowers reasoning effort, not OpenAI's billed Fast mode. The tier is read from what upstream reports, so a deployment where responses carry no service_tier prices exactly as it did in 2.11.0.