Claude Code 2.1.239 拆解:`/claude-api upgrade` 一鍵遷移,與三個「不報錯」的靜默失效
2026-08-22 這天,anthropics/claude-code 推了 2.1.239。這個版本的 changelog 有 68 條,是近期最長的一次。但真正值得你花十分鐘讀完的,不是那 68 條裡面最顯眼的新功能,而是三個「靜默失效」類型的修正——它們不會報錯、不會警告,只會安靜地什麼都不做。
如果你在 Windows 上寫過 subagent、在 monorepo 裡用過 worktree、或是專案還釘在 anthropic 0.x SDK,這一版大概率修掉了你曾經懷疑過自己、最後歸咎於「AI 就是不穩」的某個問題。
這篇把三件事拆開講清楚:/claude-api upgrade 這個新的一鍵遷移子指令實際上做了什麼、.worktreeinclude 的 **/ 為什麼會靜默匹配不到、以及 UTF-8 BOM 怎麼讓你的 agent 檔案憑空消失。每一段都給可以直接照做的指令。
本文大綱
一、/claude-api upgrade:Anthropic Python SDK 0.x → 1.x 的可執行遷移腳本
為什麼現在需要它
先講背景。Anthropic 的 Python SDK anthropic 在 2026-08-20 發布了 1.0.0(PyPI 上 info.version 為 1.0.0,upload_time 2026-08-20T19:58:58)。這是這個套件第一次跨主版號,帶了幾個會直接讓程式炸掉的破壞性變更:
requires-python從>=3.9提到>=3.10- HTTP 層從
httpx換成httpx2(PyPI 上anthropic1.0.0 的依賴寫的是httpx2<3,>=2.0.0) - 舊的 Text Completions API(
/v1/complete)整組移除 temperature/top_p/top_k從 messages 方法簽章移除
httpx2 這個名字第一次看到會讓人心裡一驚——這是不是搶註冊的假套件?我去 PyPI 查了:httpx2 的 project_urls 指向 github.com/pydantic/httpx2,author 欄位是 Tom Christie <tom@tomchristie.com>,也就是 httpx 原作者本人,目前由 Pydantic 團隊維護,版本線從 2.0 起算(我查的時候最新是 2.12.0)。所以它是官方的維護分支,不是 typosquat。Claude Code 內附的遷移文件甚至專門叮嚀 agent 要在報告裡寫一行 provenance,因為「reviewers and supply-chain scanners flag unfamiliar package names as possible typosquats」——這個細節看得出來寫的人真的踩過這個場面。
它不是「幫你改一改」,是一份 13 步的執行腳本
2.1.239 的 changelog 這樣寫:
Added
/claude-api upgradeto migrate Python projects fromanthropic0.x to 1.x, and updated the skill's Python reference for 1.x (timeouts useanthropic.Timeout, nothttpx.Timeout)
我沒有在正式專案上跑過這個遷移,但我把 2.1.239 的 binary 裡打包的那份 python/claude-api/sdk-upgrade.md 完整撈出來讀了一遍(約 29 KB)。它的設計方式很值得學:它不是給模型一段「請幫使用者升級 SDK」的模糊指令,而是一份 Step 0 到 Step 12 的可執行 checklist,開頭第一句就是:
If you arrived via
/claude-api upgrade: this is the right file. Execute the steps below in order — do not summarize them back to the user.
這句「do not summarize them back to the user」是整份文件的靈魂。它在對抗 coding agent 最常見的失敗模式:讀完一份長文件,然後跟你講一遍它讀到什麼,而不是動手做。
實際流程長這樣:
Step 0 — 先確認範圍、現況版本、目標版本。 文件明確規定:如果請求沒指名檔案或目錄,就問一個問題、給三個選項(整個工作目錄/某個子目錄/指定檔案清單)然後等。upgrade、upgrade python、「幫我升到 v1」全部算範圍不明。另外它要求在寫任何 pin 之前,先用 pip index versions anthropic 或 curl -s https://pypi.org/pypi/anthropic/json 確認 1.x 真的發布了——不准憑印象寫版號。
Step 1 — 用 grep 建 inventory,這份清單同時是最後的驗收表。 文件給了一張 signal 表,每個訊號對應到後面的哪一步。摘幾個關鍵的:
| grep 訊號 | 它會抓到什麼 |
|---|---|
import httpx, from httpx |
可能把 httpx 物件交給 SDK 的模組 |
respx, pytest_httpx, vcr, HTTPXClientInstrumentor |
會靜默停止攔截 SDK 流量的 mock / APM |
with_raw_response |
raw response 呼叫點 |
completions.create, HUMAN_PROMPT, AI_PROMPT |
已移除的 Text Completions |
temperature, top_p, top_k |
已移除的取樣參數 |
AnthropicBedrock( |
可能依賴舊 region fallback 的 Bedrock client |
它還要求對每個 hit 先分類再動手:SDK 呼叫點(改)/同名但無關(不動,例如恆溫器的 temperature 變數、urllib.parse)/測試(改,但要保持測試有意義)/文件與 notebook(改,.ipynb 要改 cell source 字串並保持 JSON 合法)。這個「先分類再編輯」的紀律,是你自己寫 migration prompt 時最該抄走的一招。
Step 3 是最容易漏的一步。 httpx → httpx2 只在物件跨越 SDK 邊界時才要改:timeout=30.0 這種純值完全不用動,但 httpx.Timeout、httpx.Limits、transport、整個 httpx.Client 當 http_client= 傳進去,就必須來自 httpx2——舊 httpx 的 client 傳進去會在建構時直接 TypeError。文件特別點名:你自己寫的中介層也算,例如 class TracingTransport(httpx.BaseTransport) 子類、wrapper 內層委派的 httpx.HTTPTransport()、httpx.Auth flow、event_hooks callable 的型別註記,全部要 re-base。
最好的做法其實是直接用 SDK 自己的 re-export,連 import 都省掉:
# 這四個都已經是 httpx2-based,簽章不變
from anthropic import (
Timeout,
DefaultHttpxClient,
DefaultAsyncHttpxClient,
DefaultAioHttpClient,
)
這就是 changelog 那句「timeouts use anthropic.Timeout, not httpx.Timeout」的意思。
而如果你的專案是應用程式(不是函式庫),還有一招更省事:
# entry point 的最前面幾行,必須在任何東西 import httpx / httpcore 之前
import httpx2
httpx2.alias_httpx()
import httpx # 現在這就是 httpx2 模組
兩條硬規則:它必須跑在任何 httpx / httpcore import 之前(否則 RuntimeError;重複呼叫是 no-op);而且只給應用程式用,絕不要塞進函式庫的 import path。
這裡有個真正陰險的坑值得單獨標紅:respx、pytest-httpx、vcrpy、OpenTelemetry 的 HTTPXClientInstrumentor、Sentry 的 httpx integration,全部是靠 patch httpx 運作的。升到 1.x 之後它們 import 依然成功、測試依然綠、trace 依然有輸出——只是再也看不到 SDK 的請求了。沒有任何東西會報錯。 修法是同一個 alias_httpx(),但要早於它們載入。pytest 的作法是掛成 early plugin:
# tests/_alias_httpx.py
import httpx2
httpx2.alias_httpx()
# pyproject.toml
[tool.pytest.ini_options]
addopts = "-p tests._alias_httpx"
pythonpath = ["."]
注意是 merge 進既有的 addopts,不是覆蓋掉。
Step 4 — .with_raw_response 的回傳型別換了。 以前是 LegacyAPIResponse,現在是 APIResponse / AsyncAPIResponse。兩個後果:async client 上 parse() / json() / text() / read() 變成 coroutine 要 await;而且 .text 和 .content 在同步 client 上也變成方法了:
| 0.x | 1.x 同步 | 1.x async |
|---|---|---|
r.parse() |
r.parse() |
await r.parse() |
r.text |
r.text() |
await r.text() |
r.content |
r.read() |
await r.read() |
.headers / .status_code / .request_id |
不變 | 不變(純屬性,不要 await) |
Step 6 — temperature / top_p / top_k 被移除,但這不是模型不支援了。 文件講得很清楚:它們是從 1.x 的簽章消失,不是從 API 消失。傳了會 TypeError。如果你的呼叫釘的是還接受這些參數的舊模型、而且程式明確依賴這個設定(有文件化的 determinism 需求、正在做 temperature A/B),正確做法是搬進 extra_body:
# Before
client.messages.create(..., model="claude-sonnet-4-6", temperature=0.2)
# After(僅限:釘的模型確實接受,且程式確實依賴)
client.messages.create(..., model="claude-sonnet-4-6", extra_body={"temperature": 0.2})
哪些模型還吃這組參數,是模型層的問題不是 SDK 層的問題。依照那份文件的描述:Opus 4.7 以後的模型只要請求帶了這三個之一(即使是預設值)就回 400;4.6 / 4.5 那條線(Opus 4.6、Sonnet 4.6、Opus 4.5、Sonnet 4.5、Haiku 4.5)以及仍在服務的已棄用 Claude 4 模型則還接受。
Step 10 — Bedrock 現在強制要 region。 AnthropicBedrock() 以前沒設 region 會警告並 fallback 到 us-east-1,現在直接在建構時丟 ValueError。文件對 agent 下了一條很好的指令:如果判斷不出部署環境有沒有提供 region,不准亂猜一個填進去,要列進報告請使用者確認。這條「不確定就升級成人類決策,而不是編一個看起來合理的值」的規則,值得抄進你自己所有的 migration prompt。
怎麼用(照做版)
# 1. 先升到 2.1.239 以上
claude update && claude --version
# 2. 在專案根目錄開 session,明確給範圍,不要給模糊指令
claude
> /claude-api upgrade python src/
# 3. 想先看不想改,就講清楚
> /claude-api upgrade python src/ — 只出報告和 diff,先不要寫檔
小提醒:upgrade 目前只有 Python 有 sdk-upgrade.md。文件明寫,如果偵測到的語言沒有對應的升級指南,agent 應該直說「沒有 bundled 指南」並指向該 SDK 的 CHANGELOG,不准拿 Python 那份改編。所以 TypeScript 專案打這個指令,正確行為是被婉拒。
另外別搞混同一個 skill 的三個子指令:migrate 是換模型、prompt-audit 是稽核舊模型時代的 prompt 寫法、upgrade 才是換 SDK 主版號。
二、.worktreeinclude 的 **/ 為什麼會靜默匹配不到

先講 .worktreeinclude 是什麼
Claude Code 的 worktree(claude --worktree feature-x、EnterWorktree 工具、subagent 的 isolation: worktree)本質是 git worktree add,所以它是一份乾淨的 checkout——你的 .env、config/secrets.json 這些被 gitignore 的檔案,在新 worktree 裡不存在。
官方文件的解法是在專案根目錄放一個 .worktreeinclude:
.env
.env.local
config/secrets.json
文件寫得很精確:「The file uses .gitignore syntax. Only files that match a pattern and are also gitignored are copied, so tracked files are never duplicated.」——注意這是一個交集:既符合你的 pattern、又確實被 git 忽略。這個設計是對的(避免重複複製已追蹤檔案),但也正是 bug 的來源。
機制拆解
我把 2.1.239 binary 裡 copyWorktreeIncludeFiles 的實作撈出來讀了。流程是四步:
- 讀
.worktreeinclude,去掉空行與#註解,建一個 gitignore matcher。 - 跑
git ls-files --others --ignored --exclude-standard --directory取得候選清單。 - 把清單拆成「檔案」和「目錄」。檔案直接用 matcher 過濾;目錄則要先判斷值不值得展開,值得的才用
git ls-files ... -- <那些目錄>再列一次。 - 逐一複製,跳過 symlink,也跳過會經由 committed symlink 逃出 worktree 的目標路徑。
關鍵在第 2 步那個 --directory 旗標。它會把整個都被忽略的目錄摺疊成一行。我在本機開了個 repo 實測:
$ cat .gitignore
secrets/
.env
build/
$ git ls-files --others --ignored --exclude-standard --directory
.env
build/
packages/
packages/api/
packages/api/secrets/
secrets/
$ git ls-files --others --ignored --exclude-standard # 不加 --directory
.env
build/out.txt
packages/api/secrets/api.json
secrets/prod.json
看到了嗎——加了 --directory,secrets/prod.json 和 packages/api/secrets/api.json 根本不在候選清單裡,只剩下 secrets/ 這種目錄項。Claude Code 必須自己決定要不要鑽進去。
bug 長在哪
2.1.239 之前,決定「要不要展開這個目錄」的判斷大致只有兩條:pattern 的字面開頭是不是這個目錄名、或 pattern 在第一個萬用字元之前的字面前綴是不是這個目錄的前綴(程式碼裡是 m.search(/[*?[]/) 且要求 index 大於 0)。
於是 **/secrets/*.json 這種 pattern 就死得很安靜:
"**/secrets/*.json".startsWith("secrets/")→false- 第一個萬用字元在 index 0,所以「字面前綴」是空字串,
index > 0的條件不成立 → 跳過 - matcher 拿
secrets/這個目錄本身去比**/secrets/*.json→ 也不匹配(pattern 要的是目錄裡的檔案)
三條都不成立 → 這個目錄不展開 → 裡面的 .json 從頭到尾沒進過候選清單 → 複製了 0 個檔案,沒有任何警告。你只會在新 worktree 裡發現 secrets 不見了,然後開始懷疑是不是自己 pattern 寫錯。
changelog 對這條的描述是:
Fixed
.worktreeincludepatterns starting with**/silently matching nothing when the target lived in a gitignored directory
修法
2.1.239 在判斷式裡加了第三條:把 pattern 開頭的 **/ 剝掉,取剩下的第一個路徑片段的字面部分,拿去跟這個摺疊目錄的每一個路徑片段做(不分大小寫的)比對——沒有萬用字元就比相等、有萬用字元就比前綴。
套回上面的例子:**/secrets/*.json → 剝掉 **/ → secrets/*.json → 第一段是 secrets(無萬用字元)→ secrets/ 的片段是 ["secrets"],命中;packages/api/secrets/ 的片段是 ["packages","api","secrets"],也命中。兩個目錄都展開,兩個 .json 都被複製。
給你的可操作結論
- 升到 2.1.239 之後,回頭檢查你
.worktreeinclude裡所有**/開頭的 pattern——它們以前可能一直是空轉的。 - 想少踩坑,能寫具體路徑就別寫
**/。config/secrets.json這種寫法從第一版就走m.startsWith(dir)那條,一直都有效。 - 每次改完
.worktreeinclude,用這條指令自己驗一次候選清單(這是 Claude Code 內部跑的第一條指令,你可以完全複製):git ls-files --others --ignored --exclude-standard --directory如果你要的檔案沒出現在這裡、而它的上層目錄被摺疊成一行,那就是需要「展開判斷」的那條路徑。
- 兩個 pattern 之外的硬限制,文件寫得很明白,別浪費時間 debug:
.worktreeinclude裡的 symlink 一律跳過;而只要你設了WorktreeCreatehook 取代預設 git 邏輯,--worktree就完全不處理.worktreeinclude,要在 hook script 裡自己複製。
三、UTF-8 BOM 讓你的 agent / skill / command 憑空消失
第三條靜默失效,是三個裡面最便宜、也最容易中的:
Fixed agents, skills, and commands whose
.mdfile starts with a UTF-8 BOM being silently ignored
Claude Code 靠 .md 開頭的 --- 判斷 YAML frontmatter。UTF-8 BOM 是三個位元組 EF BB BF,加在最前面之後,第一行就不是 --- 而是 ---,frontmatter 解析失敗,整個檔案被跳過。不是報錯,是跳過——你的 /my-command 就是不存在,.claude/agents/reviewer.md 就是不出現在 agent 清單裡。
誰會不小心加上 BOM?主要是 Windows:PowerShell 的 Out-File 與 Set-Content 在舊版預設寫 BOM、記事本另存 UTF-8 預設帶 BOM、部分編輯器的「UTF-8 with BOM」選項、以及某些從 Excel 匯出的流程。這也是為什麼這個 bug 對 Windows 使用者殺傷力遠大於 macOS / Linux。
我在本機驗了偵測與修復指令:
# 偵測:列出 .claude 底下所有開頭是 BOM 的 .md
find .claude -name '*.md' -exec sh -c \
'head -c3 "$1" | od -An -tx1 | grep -q "ef bb bf" && echo "BOM: $1"' _ {} \;
# 一次修掉
find .claude -name '*.md' -exec sh -c \
'head -c3 "$1" | od -An -tx1 | grep -q "ef bb bf" && { sed -i "1s/^\xEF\xBB\xBF//" "$1"; echo "fixed: $1"; }' _ {} \;
單檔快速確認也可以直接用 file:帶 BOM 會顯示 Unicode text, UTF-8 (with BOM) text,乾淨的顯示 ASCII text。
順帶一提,我試過 grep -rlI $'\xef\xbb\xbf' 這種寫法,在我的環境下抓不到(locale 因素),別依賴它——上面那個 head -c3 | od 的組合才穩。
根治的作法是在 repo 裡加一條 .gitattributes,並且在 Windows 端把 PowerShell 的預設編碼釘死:
# PowerShell 7+,寫進 $PROFILE
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8NoBOM'
$PSDefaultParameterValues['Set-Content:Encoding'] = 'utf8NoBOM'
如果團隊有 CI,最省事的是加一個 pre-commit 檢查,把上面那條 find 指令的偵測版接上 exit code。
四、其他三條你應該知道的修正
2.1.239 那 68 條裡,還有幾條的影響面比看起來大:
Bedrock 串流被 proxy 剝掉 Content-Type,會讓 API 呼叫數量默默翻倍。 changelog 原文:「Fixed Bedrock streaming behind proxies that strip the response Content-Type header, which silently doubled billed API calls by re-running every turn non-streaming」。這條是真金白銀——如果你在企業 proxy 後面走 Bedrock,過去的帳單可能有一半是重跑的非串流請求。這是三個「靜默」bug 裡唯一會直接反映在成本上的。
Esc 的 race condition 可能讓 agent 重複執行動作。 「Fixed a race where pressing Esc with a prompt queued could let the next turn finish early, leaving the session idle while Claude was still working and letting a later resubmit repeat actions」——排隊了 prompt 又按 Esc,session 看起來閒置但 Claude 還在跑,之後重送會重複動作。如果你有「按 Esc 打斷後重新送出」的習慣,這條值得留意。
/cost 的成本估算改了。 「Cost estimates (/cost, status line, --max-budget-usd) now include the 1.1× US-only-inference premium for data-residency workspaces」——如果你在 data-residency workspace,過去 /cost 少報了 10%。這不是漲價,是估算對齊實際計費。
五、限制與誠實標註
幾個我要講清楚的邊界:
- 我沒有在真實專案上跑過
/claude-api upgrade。 上面關於它行為的描述,全部來自兩個一手來源:2.1.239 changelog,以及我從 2.1.239 binary 裡完整撈出的那份python/claude-api/sdk-upgrade.md。實際跑起來 agent 會不會嚴格照 Step 0 問範圍,我沒有實測數據。 git ls-files --directory的摺疊行為、以及 BOM 的偵測/修復指令,是我在本機實跑驗證過的。 worktree 那段的「修法邏輯」則是讀 binary 裡的實作推出來的,不是官方文件敘述,所以我盡量描述到可驗證的層次(哪個判斷式、哪個條件不成立),而不是宣稱 Anthropic 內部怎麼想。anthropic1.0.0 的版本、日期、依賴,來自 PyPI JSON API 的一手資料(version: 1.0.0、upload_time: 2026-08-20T19:58:58、requires_python: >=3.10、httpx2<3,>=2.0.0)。SDK 的破壞性變更清單以anthropic-sdk-pythonrepo 的MIGRATION.md為準——Claude Code 內附那份文件自己也寫了「如果兩者衝突,以MIGRATION.md為準並在報告裡說明」。- 關於哪些模型還接受
temperature,我引用的是 Claude Code 內附文件的敘述,不是我實測 API 回應。 真要遷移,請自己打一次 API 確認。
六、對工程團隊的意義:一份可以今天就做的清單

這一版真正的訊號不是「多了什麼功能」,而是 Anthropic 花了 68 條的篇幅在補「不報錯的失敗」。對 coding agent 這種工具,靜默失效比 crash 危險得多:crash 你會修,靜默失效你會歸咎於「模型今天狀態不好」,然後在錯誤的方向上浪費一個下午。
今天就可以做的五件事:
claude update,確認claude --version≥ 2.1.239。- 掃 BOM:把上面那條
find指令跑一次,順便掃~/.claude/和團隊共用的 plugin repo。有 Windows 成員的團隊尤其該做,並考慮加進 CI。 - 驗
.worktreeinclude:跑一次git ls-files --others --ignored --exclude-standard --directory,確認你要帶進 worktree 的檔案,其上層目錄有沒有被摺疊。把所有**/開頭的 pattern 標記出來重新測一次;能改成具體路徑就改掉。 - Bedrock + 企業 proxy 的團隊,回頭看一下這個月的 API 呼叫量。 如果有非預期的翻倍,很可能就是那個 Content-Type 的問題。
anthropic還在 0.x 的專案,先跑一次唯讀版的/claude-api upgrade python <dir> — 只出報告和 diff,把 Step 1 的 inventory 拿到手。就算你最後決定自己改,那份 grep 清單也已經幫你把範圍框好了——特別是respx/pytest-httpx/ OpenTelemetry 那條,靠人工 review 幾乎不可能想到。
最後一個 meta 層級的收穫:如果你自己在寫 migration 類型的 skill 或 prompt,sdk-upgrade.md 這份文件是很好的範本。它做對的三件事——開頭就禁止 agent 只做摘要、先 grep 建 inventory 再分類再編輯、把「不確定的值」升級成人類決策而不是讓模型編一個——換到任何一個大規模程式碼變更任務都成立。
來源
- Claude Code CHANGELOG(2.1.239) — Anthropic:https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
- Claude Code 官方文件 · Run parallel sessions with worktrees(
.worktreeinclude規格、symlink 與WorktreeCreatehook 限制)— Anthropic:https://code.claude.com/docs/en/worktrees - anthropic-sdk-python
MIGRATION.md(0.x → 1.x 權威變更清單)— Anthropic:https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md - PyPI
anthropic1.0.0(版本、發布時間、requires_python、httpx2依賴):https://pypi.org/project/anthropic/ httpx2(httpx原作者 Tom Christie,Pydantic 團隊維護):https://github.com/pydantic/httpx2anthropics/skillsrepo(claude-apiskill 的公開版本):https://github.com/anthropics/skills- 本文中
sdk-upgrade.md與copyWorktreeIncludeFiles的引用,來自本機安裝的 Claude Code 2.1.239 binary(~/.local/share/claude/versions/2.1.239)。
整理:DataAgent · Coding Agent 實戰教學


