When Codex is on the official provider, a request carrying the proxy
placeholder key comes from one of two places: a Codex process that has
not reloaded its config, or a session created under a third-party
provider. Codex resumes a session with the provider id it recorded, so
the latter keeps using the dormant `custom` table that points at the
local proxy with the placeholder key, and restarting Codex does not
help.
The error used to suggest restarting first, which sent users of the
second case into a loop. It now says that an old third-party session
keeps its original provider and needs a new session, and falls back to
the restart hint otherwise.
Since 4.0 every quota window reads "x% left". Some users had grown used
to the 3.x "x% used" and asked for a way back, while others had asked
for "left" to match Codex itself, so this becomes a setting instead of a
flip: Settings > General > Appearance gains "Quota display" with Left
(default) and Used.
The setting is stored as quotaDisplay in the device-local settings.json
so the tray, rendered in Rust, follows it too. Only per-window
percentages change: balances, Codex Credits and saved resets keep
showing what is left, and a used-up window still reads "used up".
Colors, the "almost out" hint and which windows the card keeps are
still decided by what is left, so 94% used is orange just like 6% left.
The expanded bars now take their length from the number shown, so a
window at 30% used draws a 30% bar instead of a 70% one; balances keep
drawing what is left. SubscriptionQuotaView stays a pure view and gets
the mode from its wrappers.
Closes#8024
The stale-client check reads the process table, which Windows does not
have, so neither the model list notice nor the cached-account notice
ever appeared there, and the restart button always reported that the
daemon was not running.
On Windows the managed daemon is now found through its own daemon.pid:
the process is opened by pid and counts as running only while it is
still active and its creation time matches the recorded one, so a
reused pid is not mistaken for it. Restart is only offered for a daemon
known to be running, since Codex's restart command starts one otherwise.
The desktop app and editor plugins cannot be seen there, so the notice
falls back to "changed since you last dismissed it": the model list and
login fingerprints are recorded in codex-client-ack.json when the user
closes the notice, through the new acknowledge_codex_stale_clients
command, and a later change shows it again with wording that says
CC Switch cannot tell whether those clients are open. Without a record
the current state is taken as acknowledged, so upgrading does not show
it, and the first login seen becomes the baseline, so signing in is not
reported as an account switch.
Closing the notice used to hide it until the app, mode or provider
changed, so changing the model list again on the same provider stayed
silent on every platform. The stack view now carries a revision derived
from the same fingerprints, and the notice reappears once it changes.
Since 4.0, an official Codex login was identified by the id_token subject
alone. The Plus and Team workspaces of one OpenAI user share that subject
and differ only in tokens.account_id, so switching between their cards
was treated as "already signed in as this person": auth.json was never
rewritten and the restart notice never fired. The device login stash was
keyed the same way, so all of that user's workspaces collapsed into one
entry. 3.20.4 wrote the card's login without comparing owners, which is
why downgrading helped.
A login is now identified by user plus workspace, matching how Codex
itself decides whether the account changed.
The stash file gains a version. An older stash is upgraded once on load:
its entries are rekeyed by the new identity, and workspaces that were
merged away are restored from the official cards, but only for users
still present in the stash or in the live auth.json. A user found in
neither has logged out and is not brought back. Switching still restores
a login only from the stash, never from a card snapshot, so a logout is
not revived later either.
Refs #8054
cmd.exe decodes a .bat file with the console code page, but the batch
files are written as UTF-8. With a non-ASCII Windows username, paths
under the user profile were garbled.
Tool install/update scripts now start with `chcp 65001`. They run in a
hidden console of their own, so the code page change does not reach
the user's terminal. This fixes upgrades failing with "The system
cannot find the path specified" for npm installs under the profile.
The Claude provider launcher refers to the temporary settings file as
%~dp0<name>, since it sits next to the batch file, so the --settings
path no longer goes through the file bytes and Claude stops silently
starting with its default config. A non-ASCII project directory is
passed through CC_SWITCH_INTERNAL_CWD instead of being embedded. ASCII
directories are still written literally, so terminals that do not
inherit the caller's environment keep working as before. chcp is not
used here because it would persist in the user's interactive session.
Fixes#8034
The Codex desktop app now lists sessions from every provider, so the
unified history toggle only matters for the CLI resume list, which still
filters by the current provider id. Reword the setting description and
both enable/disable confirmations in all four locales to say so, and
note that desktop-only users don't need to turn it on.
A Codex provider whose config carries base_url (and wire_api) at the top
level with no model_provider selector was projected as a route-less card:
the editor showed an empty request address, and saving stripped both keys
as key fields with nothing to write them back, so the proxy then failed
with "Codex Provider 缺少 base_url 配置". Switching to such a card in direct
mode also sent Codex to the built-in OpenAI route.
The projection now treats a top-level base_url like the legacy
openai_base_url shape and normalizes it into [model_providers.custom], so
the editor shows the address and saving keeps it.
The form no longer produces this shape: with no selector, setCodexBaseUrl
selects a custom table and writes the address into it. The line-based
rewrite is checked against the parsed TOML and falls back to the old
top-level write (which the backend now normalizes) when it cannot be done
safely, e.g. inline tables, commented table headers or multi-line strings.
Fixes#8039
Opening "Config directory" from an app page's menu now scrolls the App configuration settings to that app's entry with a short ease-out and flashes its outline twice. Scrolling stops as soon as the user takes over (wheel, touch, pointer, scroll keys or scrollbar), is skipped under reduced motion, and only moves the settings content pane.
Some OpenAI-compatible relays hard-code the leading cache field
(cache_read_input_tokens) to a stub 0 and only report the real hit count
in prompt_cache_hit_tokens. The cache-read candidate chain stopped at the
first present value, so the stub 0 masked the real one: cache hits showed
as 0 and fresh input tokens were overcounted.
Every candidate now has to be present and non-zero; an explicit 0 falls
through to the next field, and prompt_cache_hit_tokens stays the last
fallback. An all-zero or missing chain still reads 0, and the order of
non-zero fields is unchanged, so upstreams that report a single field or
mirror the same value in several fields are unaffected.
The rule is applied on every path that reads OpenAI-format cache usage:
- Claude -> Chat conversion, streaming and non-streaming (the Usage struct
also gains prompt_cache_hit_tokens, which serde used to drop)
- Codex/Grok Build Chat-upstream usage synthesis for Responses
- the usage parser used by Codex/Grok Build passthrough
Fixes#8041
The "Send User-Agent" toggle in the OpenClaw form replaced the whole
headers object when turned on and deleted it when turned off, so every
other custom header was lost on save.
Update only the User-Agent entry: drop existing variants
case-insensitively, add a canonical User-Agent when enabled, and remove
the headers object only when it ends up empty. Detecting the toggle
state is case-insensitive as well.
Fixes#7846
The extra-options row editor keeps every value as display text, and any
row change re-parsed all rows with JSON.parse. Untouched string values
such as "123", "false" or "null" were silently turned into numbers,
booleans or null on save, in both the OpenCode and MiniMax Code forms.
Merge row edits into the stored options instead: a row whose text still
matches the stored value's display form keeps the original value and
type, and a renamed key inherits its old value. Renaming an option onto
a key that already exists is rejected, matching the model field editor.
Fixes#7833
Provider stats are grouped by (provider_id, app_type), but the result
dropped app_type. On the Claude tab, which folds Claude Code and Claude
Desktop together, a provider configured in both apps came back as two
rows with the same id and name. The table keyed rows by id:name, so
React saw duplicate keys and left stale rows behind when switching
between apps.
Return appType with each row and key table rows by [appType, providerId],
matching the provider primary key.
Fixes#8031
Codex records cache_write_input_tokens in rollout token_count events.
The session importer dropped it and billed GPT-5.6+/GPT-6 cache writes
at the plain input rate. Store it as cache_creation_tokens with
total-inclusive input semantics, matching proxy-logged Codex rows.
Proxy rows logged before v3.17.0 carry legacy input semantics and never
recorded Codex cache writes. Once rollouts report cache writes, a rebuilt
session row no longer matched such a row and the request was counted
twice. Treat a zero cache-write count on legacy Codex proxy rows as
unknown, on both the insert-time check and the read-side filter. Proxy
rows with the current semantics still require an exact match.
Fixes#7634
Claude Sonnet 5.5 and Haiku 5.5 were missing from the built-in pricing table, so their requests were costed at $0 unless models.dev sync or a manual price was set. Prefix matching only reaches longer seed ids, so neither falls back to an older row.
Prices per the official pricing page (USD / MTok):
- claude-sonnet-5-5: 2 / 10 / cache read 0.10 (0.05x, not Sonnet 5's 0.20) / cache write 2.50
- claude-haiku-5-5: 0.10 / 0.50 / 0.01 / 0.125 (the <=100K-token prompt tier; longer prompts are billed higher and are under-counted, same as other single-tier rows)
Existing installs pick the rows up via the startup INSERT OR IGNORE, and zero-cost history is recomputed by the startup backfill.
Anthropic reports the 5m/1h split of cache_creation_input_tokens in
usage.cache_creation. 1h writes cost 2x input versus 1.25x for 5m
writes, but every write was billed at the 5m price from the pricing
table. Parse ephemeral_1h_input_tokens in the proxy parsers and the
Claude session importer, and price that subset at 1.6x the cache-write
price.
Fixes#7652
The Codex stale-client check (clients still holding the model catalog
they loaded at startup) only ran while the aggregate stack view was
active, so switching between direct and route mode never warned about
running Codex app-servers.
Run the catalog check in every mode, skipping only routes that manage
their own catalog file, and show the notice whenever the backend
reports stale clients. When the CC Switch catalog pointer is removed
(leaving a route for direct official, or a route without a catalog),
clients that loaded our catalog are now reported too; clients older
than the first recorded generation are treated as fresh when no CC
Switch catalog is in use, so direct users who never entered the proxy
are not warned.
Process detection is still Unix-only; Windows remains uncovered.
Refs #7885
Co-authored-by: Jason <farion1231@gmail.com>
- Explain why Codex sub-agents on another provider cannot read encrypted tasks
and how the classic sub-agent tools setting avoids it
- Explain that Codex locks the picker to Luna Reserve once the official 5-hour
quota runs out, and describe the workaround of temporarily making a
third-party provider the default (#7945, #7962, #7974)
- Update the Chinese guide and the Chinese, English, and Japanese manuals
Codex can compress rollouts untouched for 7 days into a sibling
rollout-*.jsonl.zst and delete the original, either through
[features] local_thread_store_compression or through the app-server
rollout/compress RPC, which ignores that flag. It decompresses a rollout
back to .jsonl when the session is resumed. CC Switch only read .jsonl,
so compressed sessions vanished from the session list, their usage was
never imported, and history bucket migrations skipped them while still
recording themselves as done.
- Add codex_rollout_file, shared by the readers below: recognise both
forms, read .jsonl.zst transparently, write a rollout back in its own
form, and keep only the plain file when both forms briefly coexist.
- Session list: decompress only the head of a compressed rollout. Use
its mtime, which Codex preserves, as the last active time and show no
last-reply preview, so scanning tens of GB stays fast. Content refs are
resolved against the decompressed stream. A stale source path falls
back to the other form, and deleting a session removes both forms.
- Usage import: a cursor is inherited from any path with the same
logical file name, taking the furthest line offset, in any directory
and in both directions. Request-id dedup only covers the 30 days of
detail rows kept before rollup, and compressed rollouts are older, so
this is what prevents double counting across compress and resume.
Compressed files persist their on-disk size so unchanged ones are
skipped without decompressing.
- History bucket migrations rewrite compressed rollouts in place, keep
them compressed, and keep their mtime so old sessions do not jump to
the top of the list.
- Settings > Codex gets a "Compress old Codex sessions" switch backed
directly by config.toml (write true to enable, remove the key to
disable) through the live write engine, showing the current size of
the sessions directories. It re-reads when the saved Codex directory
changes.
The Pi preset "火山Agentplan" used the Coding Plan endpoint
(/api/coding/v3) and the Coding Plan invite link, so Agent Plan keys
failed out of the box, and Pi had no Coding Plan preset. MiniMax Code
builds its presets from Pi's list and inherited both problems.
Match the other apps: "火山 Agent Plan" now uses /api/plan/v3, and a new
"火山 Coding Plan" preset uses /api/coding/v3. The Agent Plan provider key
stays cc-switch-agentplan.
Fixes#7956
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The test added in dc93c4962 builds an on-disk SQLite fixture in a temp
dir. The WSL2 nightly points TEMP at a \\wsl.localhost 9P share, where
Windows cannot take SQLite locks, so creating the table failed with
"database is locked" on every retry. Guard it with
sqlite_unsupported_in_temp_dir() like the other SQLite fixture tests.
8919d05cb dropped the neutral switch tone but left the apps page test
asserting data-tone="neutral". Assert that the visibility switches no
longer carry a tone.
Provider search could only be opened with Cmd/Ctrl+F, so users with many
providers did not know it existed.
- Add a search button next to "Add provider" in the page header. It
toggles the existing search panel; the panel closes when switching apps
or leaving the provider page.
- Match the API address as well as name, notes, and website. The address
is read from every app's settingsConfig shape by a new shared
extractProviderBaseUrl, which ProviderCard now also uses.
- Only filter while the panel is open. Closing it with Esc used to leave
the list silently filtered.
- Portal the search panel to document.body. As a child of the list's
space-y container it counted as a sibling, so opening it pushed every
provider card down by 1rem.
- Add a "Show provider search" setting (on by default, device-local).
Hiding the button keeps the Cmd/Ctrl+F shortcut.
With the official account as the aggregation default, CC Switch fetches
the official model list with the version of the `codex` on PATH. The
server trims the list by client_version, and for 0.140 or older it only
returns hidden entries (gpt-5.5, codex-auto-review). Desktop-only users
often keep an old CLI on PATH, so the catalog was written with every
official row hidden and the picker showed only the aggregated third-party
models.
- Use the newest Codex in use: the PATH CLI or the cli_version recorded
on the 20 most recently created threads in Codex's state DB, which also
covers the desktop app and editor extensions on every platform.
- A source with no `visibility = "list"` row is not usable. When the
fetched list has none, fall back to the bundled list (no catalog if that
has none either) and show a new notice asking to update Codex.
Fixes#8014
The app row title kept only the two tiers with the least quota left. With
three windows (5-hour / weekly / monthly, e.g. Volcengine Agent/Coding
Plan, Zhipu, MiniMax) the 5-hour window resets most often and usually has
the most left, so it was almost always dropped. 3.x listed every tier.
Write all tiers in their original order. The "almost out" suffix still
follows the tier with the least left. Remove the now unused pick_lines.
Closes#8011
The neutral switch tone drew the checked track in dark gray next to a
medium-gray unchecked track, so on and off were nearly indistinguishable
in light mode. Drop the neutral tone (its only caller was the apps page
"Show" column) and let those switches use the same action color as every
other switch.
Closes#8010
The import mutation's onSettled awaited every affected refresh, including
the discover list, which downloads each repository from GitHub (up to 60s
per repo). TanStack Query only settles a mutation after onSettled resolves,
so isPending stayed true and the import dialog could not be cancelled or
closed until discover finished. The discover segment stays mounted once
opened, so this hit anyone who had visited it on a slow GitHub connection.
Only await the installed and unmanaged refreshes the dialog depends on;
repos and discover are still invalidated in the background.
Fixes#7994
Updating meant checking, then filtering the table to "updates", then
finding "Update all" in the filter row. The confirm dialog also only gave
a count, and it stayed open until every Skill had finished downloading.
- The "Check updates" button turns into "Update all (N)" once updates are
known; re-checking moves to the "..." menu. A check that finds updates
opens the confirm dialog directly instead of a toast.
- Drop the duplicate "Update all" button from the filter row.
- The confirm dialog lists each Skill with a clickable source repo that
opens the same page as the row link. V7ConfirmDialog gains a `details`
slot because the body renders inside a <p>.
- Confirming closes the dialog at once; the toolbar button shows
"Updating 1/2..." and the row menu spins until the batch is done.
- Remove the now unused `skills.updatesFound` string; add
`skillsPage.confirm.updateListAria` and `skillsPage.updatingProgress`
in all four locales.
ProxyChat and Copilot catalogs cloned the gpt-5.5 entry from Codex's own
models_cache.json (falling back to `codex debug models --bundled`, then the
bundled copy). The template only supplies the tool contract — slug, name,
context window and visibility are all overwritten — but every change
OpenAI made to that cached entry leaked into third-party models: the
hidden flag behind #7924/#7954, and newer entries carry
`tool_mode: code_mode_only`, v2 sub-agents and experimental tools the
Chat converter was never built for. The cache also announces GPT-5.5's
retirement on 2026-10-14, after which every switch would fall through to
spawning the Codex CLI.
- Always use the bundled template for ProxyChat / Copilot; drop the cache
and CLI lookups and the per-process template cache.
- Refresh the bundled template to match Codex 0.162's own gpt-5.5 entry:
`shell_type: unified_exec`, and include the skills / plugin usage
instructions (both default to false in codex-rs when absent).
- Rename the loader to `load_codex_classic_tool_template` and document
why it stays on the classic gpt-5.5 shape.
- Tests lock the classic tool contract and that a hostile
models_cache.json no longer affects generated entries.
Existing ProxyChat sessions lose the cached `comp_hash` and get compacted
once on resume.
The sidebar used the same neutral dot for two different things: tool
updates on "Apps" and a new CC Switch release on "Settings". The dot was
barely darker than the icon, and clicking "Settings" landed on General,
so the update button was still two steps away.
- The CC Switch update dot on "Settings" and on the About row is now the
success green. Tool updates on "Apps" keep the neutral dot.
- Clicking "Settings" in the sidebar while an update is available opens
About directly. Cmd+, still opens General.
- Add the Chinese aggregation guide with the final cover, client screenshots, and request-routing diagram
- Update Chinese, English, and Japanese manuals to distinguish model fetching, models.dev metadata completion, and manual entry
- Clarify official subscription support, default models, and client restart requirements; add README links
- Validate 90 local documentation links, five images, and Git diff formatting
Deleting a managed ChatGPT account (or logging out of all of them) left
every Codex provider bound to that account pointing at a dead local ID.
#7395 made such cards recoverable by hand (switch away, pick another
account); this removes the dangling bindings where they are created.
- Remove/logout detach the affected Codex providers in one SQLite
transaction: drop the binding, store an empty auth like a manual
unbind, backfill category=official for legacy cards, keep unknown
meta keys. The card then follows the native Codex login.
- Only this device's account IDs are unbound. Provider rows sync across
devices, so a row bound to another device's account stays bound.
- The command reports "account removed but unbinding failed" separately.
Any row still left dangling (failed unbinding, older versions, synced
rows) keeps going through the #7395 recovery path, whose tests now
create such rows directly.
- Frontend invalidates the Codex provider list after remove/logout,
including on partial failure.
Thanks @SaladDay for the report and the first fix in #7060.
Fixes#7055
Refs #6969
A v2 sub-agent's task arrives as the trailing agent_message whose
payload only the main agent's backend can decrypt. The opaque-state
rectifier used to replace that payload with a placeholder like any
other foreign ciphertext, so a retry sent the sub-agent an empty task.
The upstream also often rejects it inside a 200 stream, which bypassed
the rectifier entirely and left Codex retrying the same failure.
- Keep the current task untouched when rectifying; earlier agent
messages and other foreign state are still cleared.
- For native Responses passthrough requests ending in an encrypted task,
hold the stream until the first non-lifecycle event under one fixed
deadline (first-byte timeout, at most 10s), so heartbeats cannot extend
it. A self-identified rejection before output joins the normal
rectifier path; anything else replays byte for byte.
- When nothing else is left to clear, or the retry is still rejected,
return a neutral 400 unreadable_encrypted_agent_task that names the
classic sub-agent toggle, instead of letting Codex retry.
Codex multi-agent v2 has the main agent's backend encrypt the task it
hands to a sub-agent, so in aggregation mode a sub-agent on a different
provider cannot read its task. Codex picks the tool version from each
catalog row's multi_agent_version, which the aggregated catalog copies
from the official catalog (v2 for Sol and Terra).
Add a device-level setting, off by default, that stamps
multi_agent_version = "v1" on every row of the aggregated catalog
(official rows included: ciphertext minted by OpenAI is just as
unreadable elsewhere). Classic tools pass the task as plain text at the
cost of v2-only tools such as followup_task and interrupt_agent. With
the setting off the catalog is unchanged.
Changing the setting rewrites the catalog right away when Codex is in
aggregation mode and rolls the setting back if the write fails. When
config.toml forces [features] multi_agent_v2, which Codex applies before
catalog rows, the settings row says the toggle has no effect.
The all-gray quota text made it hard to tell a healthy limit from one
that is running low. Restore the v3 green / orange / red, but color only
the number ("88%", "¥82.10", "$2500", the reset count) so tier names and
"left" stay gray. Lines with no number (used up, expired, query failed)
are still colored as a whole.
- Warn once a tier has less than 20% left (was 10%), in the card and in
the tray's "almost out".
- The "used $X" line has no total, so it stays uncolored.
- The expanded bars and the auth center keep neutral numbers while
normal, since the bar already carries the color.
The OpenAI chat -> Anthropic streaming conversion only closed content
blocks on type switches and on the finish_reason path. The terminal
boundaries ([DONE] and natural stream end) flushed inline-think remnants
and emitted message_delta/message_stop without closing blocks that were
still open, so upstreams that omit finish_reason (or emit trailing
content after it) left the last text or tool block unclosed and clients
dropped its content.
Close the open non-tool block and any open tool blocks right before the
terminal events. The no-finish truncation path keeps its existing
behavior of emitting no terminal events.
Fixes#7984
Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
npm 12 blocks install scripts by default and still exits 0, so an npm-installed
Claude's `claude update` reported success while leaving a broken placeholder
binary, and the sibling-npm fallback never ran.
Prefix the primary `claude update` with npm_config_allow_scripts,
npm_config_ignore_scripts=false and npm_config_include=optional whenever the
real target lives in the @anthropic-ai/claude-code npm package. The settings
are scoped to that process tree; the fallback keeps the CLI flags from #7929.
Gating on the real path also covers npm prefixes classified as system (Intel
Homebrew node, nodejs.org pkg, ~/.npm-global, distro node), which have no
fallback at all.
Windows anchored updates and the WSL static path are not covered here.
Co-authored-by: Jason <farion1231@gmail.com>
The all-time heatmap only covered the last 53 weeks, so older usage was
cut off. It now keeps the last 53 weeks as the default view and adds a
"Show more" toggle that lists earlier history in contiguous 53-week
spans, back to the first recorded day. Each span is titled with its
exact date range and totals, and shading is bucketed across all spans
so they stay comparable.
A new get_usage_first_date command returns the earliest local day across
request logs and daily rollups, honoring the dashboard's app, provider
and model filters, so the heatmap knows how many spans to fetch in one
trends query.
Anthropic tool schemas treat `required` as an arbitrary subset of
`properties`, but the Responses backend strictifies a function tool when
`strict` is omitted: optional attributes land in `required` and the model
is forced to emit fields the client deliberately left optional (#7713 -
Claude Code Agent always emitting the optional `isolation` and losing
non-worktree teammates).
Set `strict: false` explicitly on the converted function tool in
anthropic_to_responses. The hosted web-search branch is untouched, and
`parameters` still passes through clean_schema unchanged, so the
client-side optionality semantics are preserved end to end. This branch
serves both plain Responses providers and the Codex OAuth backend.
Validation: with the production hunk reverted, the new test asserting
strict=false fails (field absent); restored, the transform_responses
suite and the full --lib run are green.
Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>