選用功能,預設關閉。關著的時候 router 跟沒有這個功能時一模一樣:不收
CONNECT、 不產生 CA,接入分頁顯示的也是原本的ANTHROPIC_BASE_URL片段。只用 CLI 就不必打開它; 它多出一把本機 CA 與一條 MITM 路徑,換來的是 Claude Desktop 也能接。
關閉時,Claude Code 主動把 API 請求送到 router(ANTHROPIC_BASE_URL);開啟時,
Claude Code 以為自己直連 Anthropic,是 router 以 HTTPS 代理的身分在半路把它接下來
(HTTPS_PROXY),解開之後交給同一套分流。
ANTHROPIC_BASE_URL 在 Claude Desktop 的 Code 分頁裡不管用。官方文檔寫明 Desktop 的
gateway 路由「從它自己的第三方推論設定讀,不讀 ANTHROPIC_BASE_URL 或 settings.json」
(LLM gateway 文檔的 Desktop app 一節)。
Desktop 自己的「Configure Third-Party Inference」會用 gateway 憑證頂掉 claude.ai 登入,
主對話就不再走訂閱,這個 router 存在的前提也就沒了。
Desktop 照讀的是代理與 CA 變數:用 claude.ai 登入的本機、SSH、WSL Code 分頁 session
「不由 app 管連線,Claude Code 從每一層設定讀這些變數,跟終端機 session 一樣」(v2.1.217 起,
network-config 文檔)。所以改從
HTTPS_PROXY 接進來。來源是 issue #2。
flowchart LR
subgraph PC[你的電腦]
CLI[Claude Code CLI]
DT[Claude Desktop]
R[router :8787]
end
CLI -- "ANTHROPIC_BASE_URL<br/>只有 /v1/messages*" --> R
R -- 主對話 --> A[(api.anthropic.com<br/>訂閱)]
R -- subagent --> P[(第三方 provider<br/>你的 API key)]
CLI -. "登入、遙測、git、npm…<br/>直連" .-> NET((網際網路))
DT -. "全部直連<br/>不經 router" .-> A
flowchart LR
subgraph PC[你的電腦]
CLI[Claude Code CLI]
DT[Claude Desktop]
subgraph R[router :8787]
C{CONNECT<br/>的目的地}
D[用本機 CA<br/>解開 TLS]
S{路徑}
end
end
CLI -- HTTPS_PROXY --> C
DT -- HTTPS_PROXY --> C
C -- "api.anthropic.com:443" --> D --> S
S -- "/v1/messages*<br/>主對話" --> A[(api.anthropic.com<br/>訂閱)]
S -- "/v1/messages*<br/>subagent" --> P[(第三方 provider)]
S -- "其他路徑、WebSocket<br/>原樣轉發、不記錄" --> A
C -- "其他主機<br/>純隧道、不解密" --> NET((網際網路))
兩張圖的右半邊一樣:/v1/messages* 走的是同一段程式(src/proxy.mjs),分流規則、改寫、
流量記錄都只有一份。差別只在請求怎麼進到 router。
sequenceDiagram
participant CC as Claude Code
participant R as router
participant A as api.anthropic.com
participant P as provider
CC->>R: CONNECT api.anthropic.com:443
R-->>CC: 200 Connection Established
CC->>R: TLS 握手
Note over CC,R: router 出示本機 CA 簽的證書,<br/>Claude Code 靠 NODE_EXTRA_CA_CERTS 信任它
CC->>R: POST /v1/messages(主對話,帶 OAuth token)
R->>A: 同一個請求,由 router 重新發出
A-->>R: SSE 串流
R-->>CC: 逐塊轉回
CC->>R: POST /v1/messages(帶 x-claude-code-agent-id)
R->>P: 換成 provider 的 key 與 model、拿掉 metadata
P-->>R: SSE 串流
R-->>CC: 逐塊轉回
CC->>R: GET /api/oauth/profile、遙測…
R->>A: 原樣轉發,不記錄
CC->>R: CONNECT github.com:443
Note over CC,R: 純隧道:TLS 在 Claude Code 與 github 之間,router 看不到內容
只把 /v1/messages* 交給分流,是為了跟關閉時看到一樣的流量:關閉時 router 本來就只收得到
/v1/messages 與 /v1/messages/count_tokens(實際的 traffic.log 4666 筆裡沒有別的)。解開之後
看得到的東西多得多,一個 claude -p 就多出 14 筆登入、feature flag、遙測,全記下來流量記錄與
進條的統計就被淹掉。
程式碼:src/connect.mjs(CONNECT、隧道、轉發)、src/ca.mjs(CA 與證書)。
| 關閉(預設) | 開啟 | |
|---|---|---|
| 能接的 client | Claude Code CLI | CLI、Claude Desktop |
| Claude Code 的設定 | ANTHROPIC_BASE_URL |
用 ANTHROPIC_BASE_URL 的 CLI 不用改;Desktop(與改過去的 CLI)用 HTTPS_PROXY + NODE_EXTRA_CA_CERTS |
| Claude Code 以為自己連到 | 自訂的 base URL | api.anthropic.com(直連) |
| 經過 router 的連線 | 只有 API 請求(/v1/messages*) |
Claude Code 與它啟動的程式的所有 HTTPS 連線 |
| router 解開、看得到內容的 | /v1/messages*(本來就是明文送來) |
所有送往 api.anthropic.com 的請求;其他主機看不到 |
| 分流、改寫、流量記錄 | /v1/messages* |
完全相同 |
| router 沒在跑時 | API 請求失敗 | 設了 HTTPS_PROXY 的 Claude Code 與它的 git / npm 全部斷網 |
| 本機 CA | 沒有 | 有,只能簽 api.anthropic.com |
| Remote Control | CLI 停用(base URL 指向非 Anthropic 主機) | Desktop 可用;CLI 要全域改用這個模式才行 |
| voice mode(CLI) | 直連 Anthropic | 經 router 原樣轉發 |
2026-09-25 在本機用一台假上游接住 router 送出的請求,跟 Claude Code 送進來的逐項比對 (量測腳本沒有進 repo):
| 請求 | 送去哪 | body | header 跟 Claude Code 送的相比 | 誰發起 TLS |
|---|---|---|---|---|
主對話 /v1/messages |
Anthropic | 原樣(規則指定換 model 時只改 model) |
多 accept-language: *、sec-fetch-mode: cors;accept-encoding 換成 gzip, deflate;client 沒帶 accept 時補 */*。這些是 Node fetch 自己加的 |
router(Node) |
subagent /v1/messages |
provider | 換 model、拿掉 metadata、修上游編不動的 pattern |
從零組起:只帶 anthropic-version、anthropic-beta、accept、content-type 與 provider 的 key。user-agent 變成 node;訂閱的 OAuth token、x-app、session id 都不會送出 |
router(Node) |
其他 api.anthropic.com 路徑(只有開啟時才經過 router) |
Anthropic | 原樣 | 原樣(只有逐跳的 connection) |
router(Node) |
| WebSocket(voice mode) | Anthropic | 原樣 | 請求頭原樣 | router(Node) |
| 其他主機(github、npm…) | 原目的地 | router 看不到 | router 看不到 | Claude Code 自己 |
前兩列兩種模式都一樣;第三、四列是開啟之後才多出來的,關閉時那些請求由 Claude Code 自己直連。
沒有人能保證不會。 這是非官方用法,Anthropic 明說不支援把 Claude Code 指向非 Claude 模型。 這個工具不偽造帳號身分、不改計費、不繞過額度,但它改變了 Anthropic 那一端看到的連線樣貌。 以下是實際的差異,判斷交給你:
- 兩種模式都一樣的:
- 帳號、OAuth token、主對話的 body 原樣送到 Anthropic。
- 主對話請求是 router 發出的:TLS 指紋是 Node(OpenSSL)的,不是 Claude Code(Bun)的; header 多了上表那幾個。
- 分到 provider 的 subagent 請求根本不會到 Anthropic。Claude Code 自己的遙測照常原樣送出, 裡面可能看得出 subagent 在活動、卻沒有對應的 API 請求 —— 這一點是推測,沒有驗證遙測內容。
- 開啟後多出來的: 登入、遙測、feature flag、Remote Control、voice 這些連線,header 雖然原樣, 但也改由 Node 發出。從 Anthropic 那端看,「TLS 指紋不像 Claude Code」的連線從只有推論請求 變成全部。
- 開啟後少掉的: Claude Code 不再知道自己設了自訂 base URL,所以不會因此停用 Remote Control。 這對判定是加分還是減分,我們不知道。
想把風險壓低:只在要用 Desktop 時才打開;分流規則只動 subagent、主對話留在訂閱;
不要設 CLAUDE_CODE_ATTRIBUTION_HEADER=0(它會讓訂閱線的輔助請求被拒,見
claude-code-request-shapes.md);
並自行對照 Anthropic 的使用條款。
GUI 接入分頁最下面的「HTTPS proxy 模式」面板,按開啟並儲存/關閉並儲存:router 當場切換,
不用重啟。 這顆鍵只存這個開關,不經過頁首的儲存鍵:畫面上其他還沒儲存的修改不會被一起存,也不會被丟掉。
(直接手改 config.json 的 "httpsProxy" 則跟其他手改一樣,下次啟動才生效;router 還在跑時手改的內容,
下一次從 GUI 存檔就被蓋掉,見 configuration.md。)
面板放在三個步驟之後而不是最上面:只用 CLI 的人不需要它,不該在第 1 步之前先讀到一個選用功能。 之前是一個勾選框加頁首的儲存鍵,勾了還沒存時,勾選框與「目前狀態」互相矛盾,要靠一段提示去調和; 改成一顆當場生效的鍵,這個中間態就不存在了。
- 打開: 第一次打開時在
config.json旁邊產生 CA,proxy 埠開始接受CONNECT。 - 關掉: 拿掉
CONNECT監聽,還開著的隧道與解開的連線一起斷掉,之後的CONNECT直接被斷線; 不再讀 CA。CA 檔案留著,下次打開不必重新設定信任。 - 先切換再存檔:切換失敗(例如 CA 寫不進去)就不存,GUI 顯示錯誤,設定檔不會跟實際狀態不一致。
面板第一行是 router 此刻實際的模式,按鍵旁邊寫著按下去會發生什麼(關掉時會斷網的警告就在鍵旁邊); 剛切換完時提醒 Claude Code 那邊還沒變。
打開不會影響原本用 ANTHROPIC_BASE_URL 的 CLI。 模式只是在同一個埠上多掛一個 CONNECT 入口,
明文送來的 /v1/messages* 照舊走原本那條路(開啟狀態下實測過:ANTHROPIC_BASE_URL 式的請求照樣轉到
Anthropic)。所以接入分頁的三個步驟在兩種模式下都是 ANTHROPIC_BASE_URL 的接法;Desktop 要的
HTTPS_PROXY 片段、放在哪一層的選擇表、以及設定打架的警告,只在模式開著時出現在最下面的面板裡。
之前的版本在開啟時把第 1 步整個換成 HTTPS_PROXY、並要人拿掉 ANTHROPIC_BASE_URL,會讓人以為一打開
CLI 就得跟著改。
要讓 Desktop 走 router,它讀得到的 settings.json 要放進新的片段、再開新的 session。關掉時:還留著
HTTPS_PROXY 的 Claude Code 的所有連線都會失敗,要把它拿掉再重開。
「關掉=原行為」的測試在 test/connect.test.mjs:createHttpsProxy 關著時不掛 CONNECT、沒打開過
就不產生 CA;執行中關掉時監聽被拿掉、開著的隧道被斷,再打開不用重啟;proxy 的 runtime 回報「沒開」時
guard 沒有例外。src/index.mjs 把兩者接起來的那段沒有測試(見「怎麼驗的」)。
模式打開之後,接入分頁最下面的面板會給出填好 CA 路徑的片段。只用 CLI 的話什麼都不用改; 要讓 Desktop 走 router,放在哪一層決定影響範圍:
全域 —— ~/.claude/settings.json(CLI 與 Desktop 共用)
{
"env": {
"HTTPS_PROXY": "http://127.0.0.1:8787",
"NODE_EXTRA_CA_CERTS": "<config.json 旁邊的 https-proxy-ca.pem>"
}
}同時拿掉 ANTHROPIC_BASE_URL。 兩個都設的話,CLI 會把 router 當成一般的 HTTP proxy,
送出請求行是完整網址的請求(POST http://127.0.0.1:8787/v1/messages)。router 會回 400,
訊息直接說是這兩個設定打架(2026-09-25 以 Claude Code 2.1.282 實測);在那之前這種請求會被組成
壞掉的網址,只看得到 fetch failed,還被重試十次。CLI 的 Remote Control 也會因為它被停用。
只在某個 repo —— <repo>/.claude/settings.local.json(不要用會提交的 settings.json:
CA 路徑是機器專屬的)
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"HTTPS_PROXY": "http://127.0.0.1:8787",
"NODE_EXTRA_CA_CERTS": "<CA 路徑>"
}
}ANTHROPIC_BASE_URL 那行是給 CLI 的:蓋掉全域那個指向 router 的值,讓 API 請求也走 proxy。
少了這行、全域又留著 http://127.0.0.1:8787,就是上面那種打架的組合,CLI 會收到 400。
Desktop 本來就不理它。專案層的 env 要在信任這個資料夾之後才生效(第一次開啟時會問)。
兩種放法的差別(2026-09-25 實測)
| 全域 | 只在 repo | |
|---|---|---|
| 分流、分到第三方 provider(CLI / Desktop) | ✅ | ✅ |
| Remote Control(Desktop) | ✅ | ✅ |
| Remote Control(CLI) | ✅ 前提是全域已經沒有 ANTHROPIC_BASE_URL |
❌ claude remote-control 只看使用者層的 ANTHROPIC_BASE_URL |
| voice mode(CLI) | ✅ | ✅ |
| Desktop 的聽寫按鈕 | 不經過 router | 不經過 router |
| router 沒在跑時 | 所有專案的 Claude Code 與它啟動的 git / npm 都斷網 | 只有這個 repo |
背景 agent(claude agents、--bg) |
官方建議把代理變數放在這一層 | 沒量過 |
設好之後重開 Claude Code(Desktop 開新的 session),/status 應該看到 Proxy 指向 router、
Additional CA cert(s) 是 CA 路徑,Login method 仍然是 claude.ai 帳號。
- 第一次打開模式時產生(啟動時設定已經是開著的,就在啟動時;在 GUI 打開,就在按下去的時候),
放在
config.json旁邊:https-proxy-ca.pem(證書)與https-proxy-ca-key.pem(私鑰,0600)。 兩個都在.gitignore裡。用ROUTER_CONFIG把設定檔放到別處時,CA 跟著過去,NODE_EXTRA_CA_CERTS也要跟著改。 - ECDSA P-256,效期十年。伺服器證書每次打開模式時重簽一張,只放在記憶體裡。
- nameConstraints 限定只能簽
api.anthropic.com。 私鑰外洩的話,拿它簽別的網域, 驗證端會以permitted subtree violation拒絕;簽 IP 位址(iPAddress SAN)則以excluded subtree violation拒絕。test/ca.test.mjs用真的 TLS 握手驗這兩件事。 只列允許的網域擋不住 IP —— RFC 5280 的 permittedSubtrees 只管它列出的名稱種類 —— 所以另外把全部 IPv4/IPv6 位址列為排除。2026-09-25 之前產生的 CA 沒有這段排除, 刪掉兩個檔案讓它重新產生即可。 - 不要裝進系統信任庫。 只透過
NODE_EXTRA_CA_CERTS給 Claude Code 用。瀏覽器不信任它, 所以就算瀏覽器的流量經過 router,也解不開。 - 載入時先檢查。 證書與私鑰不是同一對、過期、或解析不了,模式就打不開:啟動時 router 直接結束,
GUI 存檔時回報錯誤、設定不存。訊息會寫出要刪哪兩個檔案。照樣載入的話,每次握手都會失敗,
console 只看得到指向
NODE_EXTRA_CA_CERTS的警告,方向是錯的。 - 證書是
src/ca.mjs手工用 DER 拼出來的:Node 只會解析 X.509、不會產生,而這個專案零依賴。 - 刪掉這兩個檔案,下次打開模式時就會產生新的一組(重啟 router,或在 GUI 關掉再打開)。 路徑不變,但 Claude Code 要重開才會讀到新證書。
CLI(Claude Code 2.1.281)。 用 claude -p 設 HTTPS_PROXY 與 NODE_EXTRA_CA_CERTS、
不設 ANTHROPIC_BASE_URL,叫它開一個 subagent:
- debug log 有
CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS,握手成功, nameConstraints 沒有讓 Claude Code 的 TLS 堆疊拒絕。 - 主對話記成
main、subagent 記成subagent,跟ANTHROPIC_BASE_URL模式一樣。 - 原樣轉發的登入、bootstrap、遙測都正常,session 照常跑完。
- 不給 CA 時 Claude Code 自己回
SSL certificate verification failed (UNABLE_TO_VERIFY_LEAF_SIGNATURE) … set NODE_EXTRA_CA_CERTS,router 的 console 也印出提示。
Desktop(2.7032.0.0,Microsoft Store 版,內嵌 Claude Code 2.1.280)。 兩個變數只放在一個
測試資料夾的 .claude/settings.local.json,~/.claude/settings.json 裡照舊是指向另一台 router
的 ANTHROPIC_BASE_URL。在 Code 分頁(Local、claude.ai 登入)開那個資料夾,叫它開一個 subagent:
- 51 筆請求全部經過這台 router、全部 200:主對話
/v1/messages6 筆、subagent 1 筆(分類正確)、/v1/messages/count_tokens44 筆。流量記錄的 session id 對得上 Desktop 自己的 session 檔。 - 專案層的設定就夠:官方文檔說 claude.ai 登入的本機 session 每一層都讀,實測相符。
count_tokens比 CLI 多得多(CLI 在 master 的 4666 筆裡才 75 筆),推測是介面在算 context 用量。 走訂閱線,不影響分流,但會佔掉流量記錄的篇幅。- Store 版 Desktop 的資料在
%LOCALAPPDATA%\Packages\Claude_<id>\LocalCache\Roaming\Claude\(claude_desktop_config.json、內嵌的claude-code\<版本>\、claude-code-sessions\), 不在一般的%APPDATA%\Claude:MSIX 會把 AppData 重導到套件自己的目錄。
第二輪(Desktop 2.9939.2.0,CLI 2.1.282)。 測試用的 launcher 另外把 CONNECT、原樣轉發的 請求與 upgrade 印出來(只在測試時用,沒有進 repo)。
- 分到第三方 provider: 規則設成 subagent → DeepSeek。CLI 與 Desktop 各開一個 subagent,
都被送到 DeepSeek、model 改寫成
deepseek-flash、回 200;主對話照走訂閱。 - Remote Control(Desktop): 在 Desktop 的 session 裡
/remote-control,註冊與心跳全部經 router 原樣轉發(POST /v1/code/sessions、…/bridge、…/worker、…/worker/events、…/worker/heartbeat,都是 200)。從 claude.ai/code 送一句話,本機 session 收到、推論請求經 router 走訂閱、回答出現在網頁上。 - Remote Control(CLI)測不了:
claude remote-control在讀專案設定之前就拿使用者層的ANTHROPIC_BASE_URL做資格檢查,專案層覆寫成https://api.anthropic.com也壓不過;--setting-sources放在remote-control前面則直接被拒。要在 CLI 用,得從使用者層拿掉ANTHROPIC_BASE_URL,也就是整台機器改用 HTTPS proxy 的接法。 - voice mode(CLI):
/voice tap之後錄音,router 看到UPGRADE /api/ws/speech_to_text/voice_stream,Anthropic 回101,音訊經 WebSocket 送出, 轉寫結果回到 CLI 並自動送出。 - Desktop 的聽寫不走這條路: 輸入框旁的聽寫按鈕能轉寫,但 router 什麼都沒看到 ——
那是 app 自己連線,不經內嵌的 CLI,也就不吃
HTTPS_PROXY。proxy 模式影響不到它。
還沒實測: Desktop 的 SSH / WSL session(官方文檔說它們跟本機 session 一樣讀這些變數)。
自動測試(npm test,每次提交都跑,CI 在 Ubuntu / Windows × Node 20 / 22 / 24)
| 測試 | 驗的是 |
|---|---|
test/ca.test.mjs |
用真的 TLS 握手:CA 簽的證書驗得過;同一把 CA 替別的網域、或替 IP 位址簽的會被 nameConstraints 拒絕;不信任這把 CA 的 client 握手失敗;2050 年後的到期日編碼;CA 落檔後原樣讀回;落檔的證書與私鑰不是同一對、過期、或解析不了時拒絕載入,訊息寫出要刪的檔案 |
test/connect.test.mjs(開啟) |
經 CONNECT 解開的子 agent 請求分到 provider、訂閱 token 不會跟過去;主對話原樣走訂閱;其他路徑原樣轉發且不進流量記錄;明文送來、Host 寫 api.anthropic.com 的請求照樣 403;其他主機走隧道;目的地連不上回 502;解析不出目的地回 400;client 不信任 CA 時提示去查 NODE_EXTRA_CA_CERTS;WebSocket upgrade 原樣接到上游。這些都跑在 runtime 寫死「開著」的 proxy 上 |
test/connect.test.mjs(關閉) |
createHttpsProxy 關著時不掛 CONNECT、不產生 CA、CONNECT 直接被斷;在 runtime 寫死「沒開」的 proxy 上硬掛 CONNECT,解開的請求被 guard 以 403 擋下 |
test/connect.test.mjs(開關) |
打開時產生 CA、掛上一個 CONNECT 監聽,已經開著再打開不重掛(「關閉」那一列的對照組);兩次打開同時進來只掛一個監聽、落檔的證書與私鑰是同一對,兩次關掉之後 CONNECT 被斷;執行中關掉時監聽被拿掉、開著的隧道被斷、之後的 CONNECT 被斷,再打開立即可用、沿用同一把 CA |
test/admin.test.mjs |
同時送兩個 PUT /api/config,第二個等第一個存完才開始,前一個失敗不卡住後面。setConfig 是測試自己給的假函式,不含 HTTPS proxy 的切換 |
test/config-save.test.mjs |
saveConfig 同時存 20 次都成功,config.json 是完整的 JSON,不留暫存檔 |
test/proxy-errors.test.mjs |
請求行是完整網址(HTTPS_PROXY 與 http:// 的 ANTHROPIC_BASE_URL 同時設)時回 400、訊息點名這兩個設定、不往上游送 |
test/config.test.mjs |
httpsProxy 預設關閉,只有布林 true 才打開 |
沒有測試的:src/index.mjs 的接線。 guard 看的 getRuntime().httpsProxy 讀自
createHttpsProxy().enabled、setConfig 先切換再存、存檔失敗時切回原狀,這幾件事只有
src/index.mjs 在做,而它一 import 就綁埠啟動,沒有測試 import 它。上表的 guard 測試用的是寫死的
runtime,證明的是「runtime 說沒開時沒有例外」,不是「GUI 關掉之後 runtime 會說沒開」。
「關閉」與「開關」那幾列做過變異驗證:拿掉 guard 的開關判斷、讓切換無視 enabled(當時還是啟動時
一次決定的 setupHttpsProxy)、關掉時不斷隧道或不拿掉監聽、拿掉 apply 的排隊,對應的測試會紅;
改回來才綠。這一步是手動做的,沒有寫進測試。
端到端(2026-09-25,手動,細節見上面的「實測」)
- 測試用的 router 跑在另外的埠(18787 / 18788),設定檔是副本,全域設定與正在用的 router 不動;
Claude Code 那邊只在一個測試資料夾的
.claude/settings.local.json設變數。 - 真的 Claude Code:CLI 用
claude -p … --debug-file,看CA certs: Appended …等行。 - Claude Desktop:用 Orca 的 computer use 操作介面(開資料夾、送訊息、
/remote-control、聽寫), 系統資料夾選擇視窗用 Windows UI Automation 填路徑。 - Remote Control:從 Chrome 開 claude.ai/code 上的那個 session 送一句話,看本機有沒有收到、回答 有沒有回到網頁。
- voice mode:CLI 開
/voice tap,錄音時用 Windows 的語音合成從喇叭念一句話給麥克風聽。 - 看 router 做了什麼:測試用的 launcher 另外印出每個 CONNECT、每筆原樣轉發的請求與 upgrade,或包住
fetch記下送往上游的每一筆(request-map.md)。 - 「送出了什麼」那張表:一台假上游接住 router 送出的請求,跟送進去的逐項比對 header。
- GUI 用 Playwright 開關兩種模式、檢查片段是合法 JSON;文檔裡的 mermaid 圖用 mermaid 11 渲染過。
這些輔助腳本都沒有進 repo;Claude Code 或 Desktop 升級後要重驗,照上面的做法重做。
- router 對外不會再經過另一層代理。 如果你的網路要靠 Clash 之類的系統代理才連得到外面:
CONNECT 隧道(
net.connect)與 WebSocket(tls.connect)一律直連。fetch(兩種模式的/v1/messages*)與原樣轉發(https.request,走 global agent)預設也不理HTTPS_PROXY,只有 router 啟動時帶NODE_USE_ENV_PROXY=1或--use-env-proxy才會用環境變數裡的代理,而且要看 Node 版本:fetch從 v24.0.0 起、http.request/https.request的 global agent 與--use-env-proxy從 v24.5.0 起,兩者都 backport 到 v22.21.0;Node 20 沒有這個功能,設了也沒用 (Node 文檔)。 帶著這個設定、又讓HTTPS_PROXY指向 router 自己去啟動它,請求會繞回自己。 Clash 的 TUN 模式在網路層接管,不受影響。這一條是讀程式碼與 Node 文檔得出的,沒有實測。 - 不支援代理認證(
Proxy-Authorization)。router 只綁 127.0.0.1,本機任何程式都能拿它當隧道 —— 它們本來就能自己連出去,所以沒有多開放什麼。 - 只支援
CONNECT。HTTP_PROXY用的明文代理請求(請求行是完整網址,GET http://…)不會被 轉發:Host 是別的主機時 guard 回 403,Host 是 router 自己時回 400(見「設定 Claude Code」)。 所以不要把HTTP_PROXY也指過來。 - 解開的連線只講 HTTP/1.1。 router 的 TLS 沒有宣告 ALPN,Claude Code 會退回 HTTP/1.1;實測正常。
- 原樣轉發的請求與隧道不進流量記錄,GUI 上看不到它們。這是刻意的(見上面「原理」),但也代表 出問題時只能靠 Claude Code 的 debug log 查。
- Claude Desktop 的聽寫不經過 router;CLI 的 Remote Control 只有全域設定才行。
- 沒測過:Desktop 的 SSH / WSL session、背景 agent(
claude agents、--bg)、macOS / Linux 上的 Desktop、Claude Code 送 CONNECT 時就附帶資料(router 會直接斷線;目前的 client 都等 200 才開始握手)。
HTTPS_PROXY會被 Claude Code 啟動的每一支程式繼承,包括 Bash 工具裡跑的 git、npm、 curl。它們經 router 原樣隧道出去,但 router 沒在跑的時候全部斷網,Claude Code 本身也是。 不想經過 router 的主機列進NO_PROXY。- 模式開著時,guard 對解開的請求放行。 它們的 Host 是
api.anthropic.com,必然過不了主機名檢查。 這條路不必防 DNS rebinding 與 CSRF:網頁的fetch送不出CONNECT,瀏覽器也不信任這把 CA。 proxy 那台 server 只收明文,所以「socket 是加密的」就代表是這條路進來的 (src/proxy.mjs)。明文送來、Host 寫api.anthropic.com的請求照樣被擋;模式沒開時這個例外 根本不存在。test/connect.test.mjs對這兩件事都有測試,但用的是寫死開或關的 runtime;src/index.mjs把 runtime 接到實際開關的那段沒有測試(見「怎麼驗的」)。 - 握手失敗時 router 會在 console 提示去查
NODE_EXTRA_CA_CERTS。 TLS 1.3 下 client 驗不過證書只會關掉連線,server 端看不到錯誤,只看得到「還沒握完手就關了」,所以 client 正常中途離開也會觸發這個提示。解開 TLS 用的是自己包的TLSSocket:從 http server 的connect事件拿到的 socket 交給https.Server之後,握手失敗時什麼事件都不發(實測 Node 24)。