CC-switch使用代理模式在Claude界面引入codex踩坑记录
前言
提示:本文用于记录本人自己使用CC switch时遇到的问题:
随着人工智能的不断发展,AI不可或缺,之前在公司用Claude体验非常好,但是奈何实在太穷,Claude官方的token也就上班能用用,私人没钱用高级货,想着用codex试试,直接使用ccswitch代理转发,没想到全是坑
一、先说说大家最容易踩坑的地方
大家正常情况下是不是以为打开ccswitch的Claude界面开启代理,正常添加自定义配置,选择openAI接口就完事了是吧
后面再去自己的api接口找找模型列表填完,万事大吉是吧。结果捣鼓了3小时,不是503就是401报错
二、原因分析
1.上面几处配置是“Claude 自定义供应商”和“OpenAI 兼容上游”混在了一起。ccswitch 的 Claude 入口默认按 Anthropic 语义处理,不是把任意 https://…/v1 填进去就能用。它必须额外指定协议转译类型,这次真正能工作的关键字段是 apiFormat = openai_responses。日志里的 503 No available providers,本质上就是它没正确走到可用的上游协议链路。
2. 你手上的 ASXS 页面确实给了对的 key,但 ccswitch 在 Claude 接管时会把“当前 Claude live 配置里的 token”再同步回数据库。你本机之前的 Claude live 配置里还是旧的 shir token,所以我第一次虽然把 endpoint 改到了https://api.asxs.top/v1,请求也确实打到了 ASXS,但上游收到的却是旧token,于是回了 401 invalid_api_key。这也是为什么表面看起来“地址对了、模型也对了”,还是不通。
2.1如何标题排查
- 先确认 ccswitch 本地到底支持什么。我从程序和数据库里确认了 Claude provider 不只支持 anthropic,还支持openai_responses / openai_chat。
- 再确认你给的页面到底提供了什么。我直接读了页面后端接口,拿到这张 CDK 的真实 remote_api_key 和 remote_web_url
- 最后才修 ccswitch。我把当前 Claude provider 改成正确的 openai_responses 格式,同时把数据库里的当前 provider 和接管
- 备份里的旧 token 一起修掉,再用本地代理地址 http://127.0.0.1:15721/v1/messages 做真实 Claude 路径测试,并且在重启 ccswitch后又验证了一次。
三 操作指南
前面都是废话,大家要是能解决就不用来看我这个博客了,总结一下:
真正要改的,只有这 2 个地方是“主改动”,第 3 个是“检查项”。
.cc-switch/cc-switch.db
这是最核心的。要改 3 张表:
- providers
- 找 app_type=‘claude’
- 新增或更新一条 Claude provider
- 关键字段:
- settings_config.env.ANTHROPIC_AUTH_TOKEN = 你的 remote_api_key
- settings_config.env.ANTHROPIC_BASE_URL = https://api.abcdefg.top/v1
- meta.apiFormat = openai_responses
- is_current = 1
- 其他 Claude provider 的 is_current 设为 0
- proxy_config
- 找 app_type=‘claude’
- 设成:
- proxy_enabled = 1
- enabled = 1
- live_takeover_active = 1
- proxy_live_backup
- 如果这张表里有 app_type=‘claude’ 的记录,也要改
- 这里最容易坑你
- 把 original_config.env.ANTHROPIC_AUTH_TOKEN 也改成新的 remote_api_key
- 把 original_config.env.ANTHROPIC_BASE_URL 改成 https://api.asxs.top
- 不然重启 ccswitch 时,它会把旧 token 又同步回来
.cc-switch/settings.json
只改一个字段:
-
currentProviderClaude = 你刚才那条 Claude provider 的 id
这个通常不要手改,它是 ccswitch 接管后自动重写的。
正确结果应该是: -
ANTHROPIC_BASE_URL = http://127.0.0.1:15721
-
ANTHROPIC_AUTH_TOKEN = PROXY_MANAGED
也就是说:
这个文件是“结果”,不是“源配置”。
你不用改的文件:
- D:/program/CCswitch/cc-switch.exe
- .claude.json
最关键的一句话:
- 真正决定能不能用的是 .cc-switch/cc-switch.db
- 真正决定当前选中哪条 Claude provider 的是 .cc-switch/settings.json
- .claude/settings.json 只是接管后的落地结果
下面附一段我的 .cc-switch/settings.json
{
"showInTray": true,
"minimizeToTrayOnClose": true,
"enableClaudePluginIntegration": true,
"skipClaudeOnboarding": false,
"launchOnStartup": false,
"silentStartup": false,
"enableLocalProxy": true,
"proxyConfirmed": true,
"streamCheckConfirmed": true,
"enableFailoverToggle": false,
"language": "zh",
"currentProviderClaude": "asxs-claude-1775320526032",
"currentProviderCodex": "asxscodexapi-1775304677264",
"skillSyncMethod": "auto"
}
忘了说一句大概率你直接打开.cc-switch/cc-switch.db也找不到我说的“currentProviderClaude = 你刚才那条 Claude provider 的 id”
cc-switch.db(C:/Users/QQ298/.cc-switch/cc-switch.db) 是 SQLite 数据库,不是文本文件,直接双击看一定像乱码。
你有 3 种可行办法:
- 用 SQLite 可视化工具打开
- 推荐 DB Browser for SQLite
- 打开 cc-switch.db
- 看表:
- providers
- proxy_config
- proxy_live_backup
- 用 Python 直接查
在 PowerShell 里跑:
四 懒人一步到位
$ErrorActionPreference = "Stop"
# Edit only these values before running.
$ApiKey = "sk-REPLACE_ME"
$Endpoint = "https://api.asxs.top/v1"
$Model = "gpt-5.4"
if (-not $ApiKey.StartsWith("sk-")) {
throw "Set `$ApiKey at the top of the script first."
}
if (-not $Endpoint.StartsWith("http")) {
throw "Set `$Endpoint to a full URL, for example https://api.asxs.top/v1"
}
$EndpointV1 = $Endpoint.Trim().TrimEnd("/")
if ($EndpointV1 -notmatch "/v1$") {
$EndpointV1 = $EndpointV1 + "/v1"
}
$EndpointRoot = $EndpointV1 -replace "/v1$", ""
$HomeDir = [Environment]::GetFolderPath("UserProfile")
$CcSwitchDir = Join-Path $HomeDir ".cc-switch"
$ClaudeDir = Join-Path $HomeDir ".claude"
$DbPath = Join-Path $CcSwitchDir "cc-switch.db"
$SettingsPath = Join-Path $CcSwitchDir "settings.json"
$BackupsDir = Join-Path $CcSwitchDir "backups"
$CcSwitchExe = "D:\program\CCswitch\cc-switch.exe"
$ProviderId = "asxs-claude-fixed"
$ProviderName = "ASXS Claude Proxy"
if (-not (Test-Path -LiteralPath $DbPath)) {
throw "Missing file: $DbPath"
}
if (-not (Test-Path -LiteralPath $SettingsPath)) {
throw "Missing file: $SettingsPath"
}
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
throw "Python is required for this script."
}
if (-not (Test-Path -LiteralPath $BackupsDir)) {
New-Item -ItemType Directory -Path $BackupsDir | Out-Null
}
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$DbBackup = Join-Path $BackupsDir ("cc-switch_" + $Timestamp + ".db")
$SettingsBackup = Join-Path $BackupsDir ("settings_" + $Timestamp + ".json")
Copy-Item -LiteralPath $DbPath -Destination $DbBackup -Force
Copy-Item -LiteralPath $SettingsPath -Destination $SettingsBackup -Force
$Proc = Get-Process "cc-switch" -ErrorAction SilentlyContinue
if ($null -ne $Proc) {
Stop-Process -Id $Proc.Id -Force
Start-Sleep -Seconds 2
}
$PyCode = @"
import json
import sqlite3
from pathlib import Path
db_path = Path(r"$DbPath")
settings_path = Path(r"$SettingsPath")
provider_id = "$ProviderId"
provider_name = "$ProviderName"
api_key = "$ApiKey"
endpoint_v1 = "$EndpointV1"
endpoint_root = "$EndpointRoot"
model = "$Model"
conn = sqlite3.connect(db_path)
cur = conn.cursor()
settings_config = {
"effortLevel": "high",
"env": {
"ANTHROPIC_AUTH_TOKEN": api_key,
"ANTHROPIC_BASE_URL": endpoint_v1,
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-5.2-codex",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-5.4",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.3-codex",
"ANTHROPIC_MODEL": model,
},
}
meta = {
"commonConfigEnabled": False,
"endpointAutoSelect": True,
"apiFormat": "openai_responses",
}
cur.execute("UPDATE providers SET is_current = 0 WHERE app_type = 'claude'")
exists = cur.execute(
"SELECT 1 FROM providers WHERE id = ? AND app_type = 'claude'",
(provider_id,),
).fetchone()
if exists:
cur.execute(
'''
UPDATE providers
SET name = ?,
settings_config = ?,
website_url = ?,
meta = ?,
is_current = 1,
in_failover_queue = 0,
cost_multiplier = '1.0'
WHERE id = ? AND app_type = 'claude'
''',
(
provider_name,
json.dumps(settings_config, ensure_ascii=False, separators=(",", ":")),
endpoint_v1,
json.dumps(meta, ensure_ascii=False, separators=(",", ":")),
provider_id,
),
)
else:
cur.execute(
'''
INSERT INTO providers (
id, app_type, name, settings_config, website_url, category, created_at, sort_index,
notes, icon, icon_color, meta, is_current, in_failover_queue, cost_multiplier,
limit_daily_usd, limit_monthly_usd, provider_type
) VALUES (?, 'claude', ?, ?, ?, NULL, NULL, NULL, NULL, NULL, NULL, ?, 1, 0, '1.0', NULL, NULL, NULL)
''',
(
provider_id,
provider_name,
json.dumps(settings_config, ensure_ascii=False, separators=(",", ":")),
endpoint_v1,
json.dumps(meta, ensure_ascii=False, separators=(",", ":")),
),
)
cur.execute(
'''
UPDATE proxy_config
SET proxy_enabled = 1,
enabled = 1,
live_takeover_active = 1,
updated_at = datetime('now')
WHERE app_type = 'claude'
'''
)
backup_row = cur.execute(
"SELECT original_config FROM proxy_live_backup WHERE app_type = 'claude'"
).fetchone()
if backup_row:
original = json.loads(backup_row[0])
original.setdefault("env", {})
original["env"]["ANTHROPIC_AUTH_TOKEN"] = api_key
original["env"]["ANTHROPIC_BASE_URL"] = endpoint_root
cur.execute(
"UPDATE proxy_live_backup SET original_config = ? WHERE app_type = 'claude'",
(json.dumps(original, ensure_ascii=False, separators=(",", ":")),),
)
settings = json.loads(settings_path.read_text(encoding="utf-8"))
settings["currentProviderClaude"] = provider_id
settings_path.write_text(
json.dumps(settings, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
conn.commit()
conn.close()
"@
$TmpPy = [System.IO.Path]::GetTempFileName() + ".py"
Set-Content -LiteralPath $TmpPy -Value $PyCode -Encoding UTF8
try {
python $TmpPy
if ($LASTEXITCODE -ne 0) {
throw "Python update failed."
}
} finally {
Remove-Item -LiteralPath $TmpPy -Force -ErrorAction SilentlyContinue
}
if (Test-Path -LiteralPath $CcSwitchExe) {
Start-Process -FilePath $CcSwitchExe | Out-Null
Start-Sleep -Seconds 4
}
try {
$Headers = @{
"x-api-key" = "PROXY_MANAGED"
"anthropic-version" = "2023-06-01"
"content-type" = "application/json"
}
$Body = @{
model = $Model
max_tokens = 64
messages = @(
@{
role = "user"
content = "Reply with exactly OK."
}
)
} | ConvertTo-Json -Depth 10
$Result = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:15721/v1/messages" -Headers $Headers -Body $Body -TimeoutSec 90
$Reply = $null
if ($Result.content -and $Result.content.Count -gt 0) {
$Reply = $Result.content[0].text
}
Write-Host ""
Write-Host "Success."
Write-Host "Provider: $ProviderName"
Write-Host "Model: $Model"
Write-Host "Endpoint: $EndpointV1"
Write-Host "Proxy reply: $Reply"
Write-Host "DB backup: $DbBackup"
Write-Host "Settings backup: $SettingsBackup"
} catch {
Write-Warning "Local update finished, but proxy verification failed."
Write-Warning $_.Exception.Message
Write-Host "DB backup: $DbBackup"
Write-Host "Settings backup: $SettingsBackup"
exit 1
}
用法很简单:
给文件命名,就命下面这个也行,位置是自己的
- 打开 setup-ccswitch-asxs-claude-local.ps1
- 只改顶部 3 行
$ApiKey = “sk-REPLACE_ME”
$Endpoint = “https://api.asxs.top/v1”
$Model = “gpt-5.4”
- 保存
- 在 PowerShell 运行
powershell -ExecutionPolicy Bypass -File C:\Users\QQ298\setup-ccswitch-asxs-claude-local.ps1
它会做这些事:
- 备份 cc-switch.db
- 备份 settings.json
- 把 Claude 当前 provider 改成 ASXS Claude Proxy
- 把协议设成 openai_responses
- 打开 Claude 接管
- 重启 ccswitch
- 最后用本地 127.0.0.1:15721 做一次 OK 测试
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)