公司配的筆電,Claude Code 裝完,第一次打 claude 下去,第一個請求就死在憑證錯誤上。同一個版本在家裡那台機器跑得好好的。

你去問 IT,得到的回覆是公司有做 TLS 檢查,根憑證早就推到每一台機器了。

推是推了。它就是不認。

export 完沒生效,不是你設錯,是設得太晚

最直覺的動作是補 proxy。你開一個新分頁 export HTTPS_PROXY=http://proxy.example.com:8080,切回原本那個還開著的 session 再送一次,錯誤一模一樣。

這裡有一條規則寫在官方那頁的第一段,但你通常要撞過一次才會回頭去讀它:shell 裡 export 的環境變數,Claude Code 在啟動時讀一次。原文是「Variables exported in your shell are read once at startup, so a running session doesn’t pick up later changes to your shell environment」。一個跑起來的 session 不會回頭看你的 shell 後來變成什麼樣子。

所以第一條路的方向沒錯,錯的是時機。關掉重開,export 完再啟動,proxy 那一關就過了。

順帶一提,這批設定裡唯一會在啟動當下被檢查的就是 proxy URL。值 parse 不出來(典型是漏掉 http:// 這個 scheme),Claude Code 會直接讓啟動失敗,而且錯誤訊息會指名是哪一個變數。其他的都不驗,你要等到某個請求炸了才會知道。

憑證明明裝在系統裡,它就是讀不到

proxy 通了,憑證那關還是不過。

你打開鑰匙圈確認過一次,公司的根憑證確實在裡面,也確實標成信任。IT 沒騙你。

第二條死路藏在執行環境。Claude Code 預設同時信任兩份東西:它自己打包的那套 Mozilla CA,加上作業系統的信任庫。但讀作業系統那一份有前提,要 runtime 提供 tls.getCACertificates。原生安裝器裝的一定有;用 npm 裝的,需要 Node 22.15 或更新。低於那個版本,實際生效的只剩內建那套跟 NODE_EXTRA_CA_CERTS

也就是說,憑證在不在系統裡從來不是重點。重點是你這個 runtime 讀不讀得到它。這種壞法特別討厭,因為每一個你想得到的檢查都會回報正常:憑證在、信任旗標對、路徑沒錯,然後連線照樣失敗。

站在那個畫面前要怎麼確診?先查一個數字,node -v。這一關只有 npm 裝的會卡,原生安裝器裝的不用查。真的想不起來自己當初是哪一種裝法也不必糾結:直接設 NODE_EXTRA_CA_CERTS,它在兩種情況下都生效,是這裡唯一不用先確診就能走的一條。

要嘛把 Node 升上去,要嘛別繞了,直接指:

1
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem

想更省事還有一把開關。CLAUDE_CODE_CERT_STORE 吃逗號分隔的清單,認得的值只有 bundled(內建的 Mozilla CA 集)跟 system(作業系統信任庫),預設是 bundled,system。要縮成只信任其中一邊就寫單一個值。它沒有自己的 settings.json schema key,只能走 env 區塊或直接放在 process 環境裡。

該搬家的其實是設定本身

到這裡為止都還在修症狀。真正該換的是這批設定住在哪裡。

它們全部可以寫進 settings.jsonenv 區塊,而那才是它們該待的地方:

1
2
3
4
5
6
7
8
9
{
"env": {
"HTTPS_PROXY": "http://proxy.example.com:8080",
"NO_PROXY": "localhost,192.168.1.1,.example.com",
"NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/corp-ca.pem",
"CLAUDE_CODE_CLIENT_CERT": "/path/to/client-cert.pem",
"CLAUDE_CODE_CLIENT_KEY": "/path/to/client-key.pem"
}
}

NO_PROXY 空白分隔或逗號分隔都吃,* 代表整個繞過。小寫變體也認得,優先序是 https_proxyHTTPS_PROXYhttp_proxyHTTP_PROXY,取第一個有值的。至於 loopback 上的 WebSocket 連線,Claude Code 本來就不會送進 proxy,localhost::1127.0.0.0/8 都不用特地寫進去。

兩件事先講清楚,免得你白設一輪。SOCKS proxy 不支援,文件寫得很乾脆。NTLM、Kerberos 這種需要進階認證的 proxy 也不在射程內,官方建議是改走支援該認證方式的 LLM gateway。

我的立場是:在公司網路後面,這些變數不該只活在 .zshrc 裡。如果你只是自己開互動式 session、從來不用背景 agent、也沒有人要照著你的步驟重現環境,那 export 一次確實就夠了,下面這段可以跳過。

背景 agent 是那條分水嶺。它不跑在派它出去的那個終端機裡,而是掛在一個 per-user 的 supervisor 底下,那個 process 會活得比你的 shell 久,而且全部終端機共用同一個。它繼承的是「第一個把它冷啟起來的那個 shell」的環境;如果 supervisor 是作業系統裝的服務,它根本收不到任何 shell 環境。

於是你會遇到那種最難查的症狀:同一台機器,同一組變數,有時候通有時候不通,取決於今天是哪個視窗先把 supervisor 叫起來的。

怎麼認出自己中的就是這一種?看失敗落在哪裡。你在終端機裡開互動式 session 一切正常,只有丟給背景 agent 的工作卡在憑證或 proxy 上,那就不是設定錯了,是設定沒走到那裡。同一組變數在兩條執行路徑上給出兩種結果,這件事本身就在講「環境是繼承來的」。

而且它不會自己好。supervisor 已經用錯的環境冷啟過一次,之後每一個背景 session 都吃那一份,你回頭改 .zshrc 它不會知道。再 export 一次是沒有用的,你得讓它重新起一次;或者一開始就別讓設定住在 shell 裡。

設定檔是唯一能穩定送達每一個背景 session 的路徑。

「有那一列」不等於「它載進去了」

設完之後怎麼知道生效了?這一段是整篇我最想講的。

第一條路是 debug log。claude --debug 啟動,但輸出不會印在終端機上,它落在 ~/.claude/debug/<session-id>.txt,也可以用 --debug-file <path> 指到別的地方。你要找的是這三行:

1
2
3
CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS (/etc/ssl/certs/corp-ca.pem)
mTLS: Loaded client certificate from CLAUDE_CODE_CLIENT_CERT
mTLS: Loaded client key from CLAUDE_CODE_CLIENT_KEY

讀不到檔案的話,對應位置會換成 Failed to readFailed to load 開頭的那一行,後面帶原因。

第二條路是互動模式裡跑 /status。看起來更方便,但這裡藏了一個很值得記住的差別。

/statusmTLS client certmTLS client key 這兩列,只在檔案真的載入成功時才會出現。缺一列,就是那一份沒載進去,去 debug log 找原因。這種列是好證據:有它就代表事情成了。

Additional CA cert(s) 那一列不一樣。它顯示的是 NODE_EXTRA_CA_CERTS 的路徑,而且不檢查那個檔案到底有沒有載起來。你打錯路徑,它照樣把那個錯的路徑印給你看。

同一個畫面,兩種列,證據強度天差地遠。一種是「載成功才會出現」,另一種是「你填了什麼就印什麼」。把後者當成前者來讀,你會得到一個看起來很有依據的錯誤結論——而錯誤結論裡最難自己抓的,就是這種有畫面可以指的。要確認 CA,只能回 debug log。

白名單到底要開幾個洞

防火牆那張表,官方列了十幾個 host。挑幾個真的會咬人的講:

網域 少了它會怎樣
api.anthropic.com API 請求、feature flag、WebFetch 的網域安全檢查全掛
claude.aiplatform.claude.com 登入拿不到 token;OAuth 的交換、更新、撤銷都走 platform.claude.com
registry.npmjs.org plugin 安裝、npx 起的 MCP server、npm/bun 版本的 Claude Code 自己
bridge.claudeusercontent.com Claude in Chrome 擴充的 WebSocket 橋接
*.frame.claudeusercontent.com Artifact 內容讀取
mcp-proxy.anthropic.com 來自 claude.ai 的 MCP 連接器

表上另外那兩個 Datadog 的 intake host 只載營運遙測,可以用 DISABLE_TELEMETRY 之類的開關關掉,關掉就不必放行。

還有一個很容易漏的。你以為改走 LLM gateway、把 ANTHROPIC_BASE_URL 指出去之後,api.anthropic.com 就不需要了。fast mode 的可用性檢查還是打那個 host,不是打你的 gateway base URL。所以 proxy 的白名單裡那一條得留著。

換憑證那天會發生什麼事

mTLS 還有第三個變數 CLAUDE_CODE_CLIENT_KEY_PASSPHRASE,給加密過的私鑰用。真正值得單獨記一下的是輪替行為。

Claude Code 不監看這兩個檔案。你把檔案換掉的那一刻,它什麼都不會做。它會在兩種時機重讀:一是某次 API 請求撞到連線層的錯誤(連線被重設、TLS handshake 失敗)之後重試時,二是下一次套用設定的時候,哪個先到算哪個。v2.1.232 之前連第一種都沒有。

推論一下就會發現有個縫。如果你的 gateway 是把 handshake 完成、然後回一個 HTTP 錯誤,那不算連線層失敗,它不會重讀。這種情況下新憑證要等到下次套用設定或重啟才進得去。

還有一種是輪替寫到一半被讀到,憑證跟金鑰對不起來,它會沿用舊的那一對,下次失敗再讀。想整個關掉連線錯誤重讀的行為,有 CLAUDE_CODE_DISABLE_MTLS_RELOAD_ON_STALE_CONNECTION=1

這篇的變數名、路徑與行為全部照 Claude Code 官方的 Enterprise network configuration 文件寫,是 2026 年 9 月的版本。我沒有一個裝了 TLS 檢查代理的企業環境可以驗,所以上面沒有任何一句是「我設完就通了」。網域那張表也會隨版本增減,照著開之前自己對一次原文。

回到那台筆電

現在同樣的畫面再來一次:第一個請求死在憑證錯誤。

我不會先去碰 shell。我會打開 ~/.claude/settings.jsonenv 區塊,把 proxy 跟 CA 路徑寫進去,然後 claude --debug 重啟一次,去 ~/.claude/debug/ 底下找那三行。三行都在,才算設定真的進去了;/status 那列 CA 路徑看起來再漂亮,都不算。

真正省時間的不是記住這幾個變數叫什麼,是先分清楚兩件事:設定住在哪裡(shell 還是設定檔),以及你手上那個證據能證明到哪一步。前者決定它會不會送達背景 agent,後者決定你是在除錯,還是在安慰自己。