Claude Code v2.1.251 拆解:模型切換 hook 與快取/花費可視化,把隱形成本變成可讀數字
你在 Claude Code 裡打 /model,從 Sonnet 換到 Opus,畫面上就是一行字。下一輪 Claude 想了很久才吐出第一個 token,你以為是網路慢——其實是那條 20 萬 token 的對話歷史,剛剛整段被重新讀了一遍,而且是照 cache write 的價錢算。
anthropics/claude-code v2.1.251 這版做的事,可以用一句話講完:把「換模型」這個一直是隱形成本的動作,變成可攔截、可確認、可量測的事件。它加了 PreModelSwitch / PostModelSwitch 兩個 hook 事件,加了一行 Prompt cache (main) 的快取統計,加了給 status line 用的 prompt_cache 物件,還加了 spend limit 進度條。這篇把機制拆開講,並給你可以直接貼進 settings 的設定。
先講清楚我的位置:我機器上跑的是 v2.1.239,沒有實跑過 2.1.251。以下機制全部來自 changelog 原文與官方文件,我會標明哪些是文件明寫、哪些是我依既有 hook 慣例推的。

本文大綱
一、先搞懂:你的錢是怎麼被燒掉的
Claude Code 每一輪對話,送給 API 的請求長這樣:tools → system → messages。prompt cache 是對「前綴」做快取——只要前綴一個 byte 不同,快取就讀不到,整段要重算。
Anthropic 官方 prompt caching 文件列了一份「會打掉快取的動作」清單,其中跟你日常操作最相關的是這幾條:
- 切換模型。官方文件寫得很直白:「Each model has its own cache.」用
/model換模型,下一個請求會把整段對話歷史重讀一次,即使內容一模一樣。 - 改 effort level。快取的 key 除了模型還包含 effort level,所以
/effort也是全 miss。(例外:把 effort 設成「已經生效的那個值」時,Claude Code 會保留快取。) - 開 fast mode。fast mode 會加一個 request header,那個 header 是 cache key 的一部分。一個 conversation 只付一次,但在長 session 中途才開,比一開始就開貴得多。
opusplan設定:plan mode 用 Opus、執行用 Sonnet,所以每一次進出 plan mode 都是一次模型切換、一次全新快取。這條最容易被忽略。- Fable 5 / Opus 5 的 automatic model fallback:安全分類器攔下請求後改用 fallback 模型重跑,那也是一次模型切換。
- 另外還有:MCP server 連線/斷線(在 tool 沒被 tool search 延後載入時)、
/compact、升級 Claude Code 版本、加上整個工具的 deny rule。
價差有多大?照 Anthropic platform 文件的倍率:5 分鐘 TTL 的 cache write 是基礎 input 價的 1.25×,1 小時 TTL 是 2.0×,而 cache read 只要 0.1×。也就是說,一次全 miss 的代價,是把「本來只要付 0.1 倍的 token」變成「要付 1.25 到 2 倍」——同樣的內容,帳單差 12.5 到 20 倍。
算一次給你看
假設你在一個已經跑到 12 萬 token 的 session 裡,按下 /model 從 Sonnet 換到 Opus。
- 不切換:下一輪的 12 萬 token 走 cache read,付 0.1× 基礎 input 價。
- 切換之後的第一輪:這 12 萬 token 沒有任何 cache hit,要重新處理並重新寫進 Opus 自己的快取,付 1.25×(5 分鐘 TTL)或 2.0×(1 小時 TTL)。
以 Opus 5 基礎 input $5 / MTok 計算:cache read 是 $0.50 / MTok,5 分鐘 TTL 的 cache write 是 $6.25 / MTok,1 小時是 $10 / MTok。12 萬 token 的差距,就是大約 $0.06 變成 $0.75 到 $1.20。單看一次不痛,但如果你一天用 opusplan 進出 plan mode 二十次、每次都在十萬 token 級的 session 裡,這筆就不是零頭了——而在 v2.1.251 之前,你完全沒有辦法在 CLI 裡看到這個數字。
(價格倍率來自 Anthropic platform 的 prompt caching 文件;$5 / MTok 是文件裡拿 Opus 5 當範例的基礎 input 價,實際請以你的方案與 官方定價頁 為準。)
在 v2.1.238 之後,Claude Code 已經會在你按 /model 且快取還熱著的時候跳出確認。但那是一個寫死的、只有「要/不要」兩個選項的對話框。v2.1.251 做的就是把這個決策點開放出來。
二、PreModelSwitch / PostModelSwitch:把確認框變成你的程式
CHANGELOG 對這條的原文是:
Added
PreModelSwitchandPostModelSwitchhook events (block, confirm, or annotate a model switch);SessionStartresume hooks now receive session staleness and the estimated re-cache cost
拆成三件事:
1. PreModelSwitch 支援 block、confirm、annotate 三種回應。 這在 Claude Code 的 hook 體系裡是個新組合。既有事件裡,PreToolUse 用 hookSpecificOutput.permissionDecision 表達 allow / deny / ask / defer,UserPromptSubmit 這類則用頂層 decision: "block" + reason。model switch 的三種語意剛好對應 deny / ask / 放行但附註。
2. PostModelSwitch 是事後事件,跟 PostToolUse、SessionEnd 一類,用來做記錄與副作用:寫一筆 log、發個 Slack、更新你自己的成本儀表板。
3. SessionStart 在 resume 時多拿到兩個資訊:session 有多舊(staleness)、以及預估的 re-cache 成本。這條對應官方文件裡那句警告——resume 一個升級過版本的舊 session,整段歷史都在新的 system prompt 後面,沒有任何 cache hit,而且成本隨對話長度線性成長,可能是你送出過最貴的一個請求。現在這個數字會直接送到你的 hook 手上。
誠實標註:官方文件目前查不到這兩個事件
我在 2026-08-29 抓了 code.claude.com/docs/en/hooks(含 .md 原始檔)與 llms.txt 索引,grep -i modelswitch 零命中。hooks 文件目前列出的事件仍停在 SessionStart、PreToolUse、PermissionRequest、PreCompact、Elicitation 那一組。同樣地,SessionStart 的 input schema 也還沒收錄 staleness / re-cache cost 欄位。
也就是說:事件名稱與能力來自 changelog(primary source,可信);確切的 JSON 欄位名稱,文件還沒公布。
那就自己把 schema 挖出來
不要等文件。Claude Code 的 hook 一律是把 JSON 從 stdin 餵給你的指令,所以最快的做法是裝一個「什麼都不做,只把 stdin 存起來」的 hook:
{
"hooks": {
"PreModelSwitch": [
{
"hooks": [
{
"type": "command",
"command": "tee -a ~/.claude/model-switch-payload.jsonl > /dev/null"
}
]
}
],
"PostModelSwitch": [
{
"hooks": [
{
"type": "command",
"command": "tee -a ~/.claude/model-switch-payload.jsonl > /dev/null"
}
]
}
]
}
}
貼進 ~/.claude/settings.json,重開一個 session,按幾次 /model 和 /effort,然後:
jq -s '.[0] | keys' ~/.claude/model-switch-payload.jsonl
jq -c . ~/.claude/model-switch-payload.jsonl | head -5
三分鐘就拿到真正的欄位表,比讀任何二手教學都準。順帶一提,tee 這招對任何新 hook 事件都適用,值得養成習慣。
攔截範例(欄位待你用上一步的結果校正)
假設 payload 有 from_model / to_model 之類的欄位,一個「在快取還熱、對話還長的時候,擋掉往上換大模型」的 hook 大概長這樣:
#!/usr/bin/env bash
# ~/.claude/hooks/guard-model-switch.sh
# 欄位名稱依你 tee 出來的真實 payload 調整
input=$(cat)
to=$(jq -r '.to_model // .model.id // empty' <<< "$input")
# 從 transcript 或你自己的記帳判斷是否值得付這次 re-cache
# 這裡用最簡單的規則:切到 opus 一律要求二次確認
case "$to" in
*opus*)
jq -n '{
decision: "block",
reason: "切到 Opus 會讓整段歷史 cache miss。確定要付這筆的話,先 /compact 再切。"
}'
;;
*) exit 0 ;;
esac
decision / reason 是 Claude Code 既有事件(UserPromptSubmit、PostToolUse、PreCompact…)的通用阻擋格式,我推測 model switch 走同一套;但 changelog 特別點出 confirm 這個中間狀態,所以也可能是 hookSpecificOutput 底下一個類似 permissionDecision: "ask" 的欄位。跑一次 tee 就知道。
比起硬擋,我更推薦的第一版是 annotate:不阻止你切,但每次切的時候把「這次大概要重讀多少 token」寫進 log。累積兩週,你會很清楚自己一個月在無意識的模型切換上燒了多少。
三、/usage 的 Prompt cache (main):這次是文件有寫的
跟 hook 相反,可視化這部分官方文件已經寫得很完整。
/usage 最上面的 Session block 現在多一行(需要 v2.1.251 以上),文件給的範例長這樣:
Prompt cache (main): 14 requests · 91% of input tokens from cache · 2 misses
(last 6m 10s ago, 310.2k tokens re-cached) · 1 expected rebuild
(compaction or tool-result clearing) · warm (1h TTL, last activity 40s ago)
三個部分的定義,文件講得很精確,值得抄下來:
- Misses:重新處理了本來可以從快取讀到的內容的請求。判定門檻是重算超過 5%、且至少 2,000 tokens。低於這個門檻不算 miss——所以這個數字不會被雜訊灌水。
- Expected rebuilds:Claude Code 自己重寫對話造成的 miss(compaction、清掉舊的 tool result)另外算一類,不混進 misses。這個設計很重要:它把「你的操作造成的浪費」跟「系統本來就要做的事」分開了。
- Warm / cold:目前前綴還在不在 TTL 內,並標出生效的 TTL。冷掉的話會顯示已閒置多久;如果 API 根本沒回報 cache token,這行會直接寫
no prompt caching reported by the API。
這些數字是從 API response 的 cache token 欄位算出來的,所以在任何 provider 與 gateway 上都能用。/clear 會跟 Session block 一起重置。

四、把它接到 status line:一行就看得到
同一份統計,status line 腳本可以從 prompt_cache 物件讀到。官方文件給的完整欄位(v2.1.251 起):
| 欄位 | 意義 |
|---|---|
warm |
前綴是否還在 TTL 內 |
caching_observed |
這個 session 有沒有任何一次回報過 cache token;false 代表快取關閉或你的 provider/gateway 不回報 |
ttl |
目前前綴的 TTL:"5m" 或 "1h" |
expires_at |
前綴變冷的時間(epoch 秒) |
requests / misses |
主對話的請求數 / 判定為 miss 的請求數 |
expected_rebuilds |
compaction 或清 tool result 造成的重建次數 |
hit_ratio |
cache read tokens 佔全部 input tokens 的比例(分母含 read + write + 未快取 input) |
cache_write_tokens |
這個 session 寫進快取的總 token |
miss_recache_tokens |
被判定為 miss 的那些請求寫回快取的 token |
last_miss_at |
上一次 miss 的時間 |
recache_tokens_if_cold |
如果快取已經冷掉,下一個請求要重寫多少 token |
最後那個 recache_tokens_if_cold 是我認為最實用的一個:它直接告訴你「現在離開座位、一小時後回來,要付多少」。
一支夠用的 status line:
#!/usr/bin/env bash
# ~/.claude/statusline.sh → chmod +x,然後在 settings.json 指到它
input=$(cat)
model=$(jq -r '.model.display_name' <<< "$input")
dir=$(jq -r '.workspace.current_dir | split("/") | last' <<< "$input")
cost=$(jq -r '.cost.total_cost_usd | . * 100 | round / 100' <<< "$input")
# 快取:沒有這個物件時整段跳過(第一次 API response 前不存在)
cache=$(jq -r '
if .prompt_cache == null then ""
elif .prompt_cache.caching_observed == false then " | cache off"
else
" | cache " +
(if .prompt_cache.hit_ratio == null then "--"
else ((.prompt_cache.hit_ratio * 100) | round | tostring) + "%" end) +
(if .prompt_cache.warm then " warm(" + .prompt_cache.ttl + ")" else " COLD" end) +
(if (.prompt_cache.misses // 0) > 0 then " ✗" + (.prompt_cache.misses | tostring) else "" end) +
(if (.prompt_cache.recache_tokens_if_cold // 0) > 0
then " ~" + ((.prompt_cache.recache_tokens_if_cold / 1000) | round | tostring) + "k@risk"
else "" end)
end' <<< "$input")
# spend limit:只有在 Claude apps gateway 且有設額度時才存在
spend=$(jq -r '
if .rate_limits.spend_limit == null then ""
else " | spend " + ((.rate_limits.spend_limit.used_percentage) | round | tostring) + "%"
end' <<< "$input")
printf '%s | %s | $%s%s%s' "$model" "$dir" "$cost" "$cache" "$spend"
兩個防呆重點,文件有明講、一定要照做:
prompt_cache在主對話第一次 API response 之前不存在,所以一律用// empty或== null檔掉。rate_limits只有 Pro / Max 訂閱者、或在有設 spend limit 的 Claude apps gateway 後面才會出現,而且resets_at過期後 Claude Code 會直接把那個 window 拿掉。三個 window(five_hour、seven_day、spend_limit)各自可能缺席。
五、spend limit bar:只有一群人吃得到
/usage 的 spend limit 進度條與 rate_limits.spend_limit 欄位,適用條件是 changelog 原文寫的:「for developers behind a Claude apps gateway with spend limits」。文件補充了一個細節:它的 used_percentage 可以超過 100(其他 window 是 0–100 封頂),因為你有可能超支。
所以如果你是個人 Max 訂閱、或直接用 API key,這條對你沒用,不用花時間去接。這是這一版裡受眾最窄的一個功能,但對有 gateway 的公司來說,它讓「這個月的額度燒到哪了」第一次能被寫進每個人的 status line,而不是只有 admin 在 console 看得到。
六、限制:這版沒解決什麼
誠實列一下邊界,避免你抱著錯的期待去裝:
- 只算主對話,不含 subagent。 文件明寫「Claude Code doesn't count subagent requests in these statistics」。如果你重度使用 subagent、workflow、agent teams,那部分的快取行為在這行統計裡是隱形的。而且 subagent 預設走「everything else」bucket,訂閱制下只有 5 分鐘 TTL。
- 需要 provider 回報 cache token。
caching_observed: false就代表你的 gateway 或 provider 沒吐這些欄位,這行統計會直接失效。Bedrock 上 prompt caching 的支援、最小可快取長度、1 小時 TTL 可用性都因模型而異。 - 1 小時 TTL 不是到處都有。 官方文件:Claude apps gateway 不支援 1 小時 TTL;透過
ANTHROPIC_BASE_URL的自架 gateway 則要設定成原樣轉發anthropic-betaheader。 - hook 只是攔截,不會幫你省。
PreModelSwitch不會讓切換變便宜,它只是讓你在付錢前看見價目表。真正省錢的是你切換前先/compact,或乾脆不要在長 session 中途切。 - hook 欄位未文件化。 前面說過了,先
tee再寫邏輯。
七、對工程團隊:今天就能做的四件事
1. 先量,再管。 只裝 status line 的 prompt_cache 顯示,跑一週。多數人會發現自己的 hit_ratio 比想像中低,而 misses 集中在幾個固定動作上(通常是 opusplan 進出 plan mode、以及中途開 fast mode)。沒有基線就開始裝 hook 擋人,只會惹毛同事。
2. 檢查你是不是在用 opusplan。 如果是,你每次進出 plan mode 都在付一次全 miss。折衷做法:在 plan mode 裡把計畫一次想完整,減少來回切換的次數。
3. 把 TTL 設對。 用 API key 或雲端 provider 的人,主對話預設只有 5 分鐘 TTL;訂閱制在額度內才自動給 1 小時。要拉長就設 promptCacheTtl: "1h"(或 CLAUDE_CODE_PROMPT_CACHE_TTL=1h,v2.1.242 起)。注意 trade-off:1 小時 TTL 的 write 是 2.0× 而非 1.25×,短衝刺、不會 idle 超過五分鐘的工作模式,開 1 小時反而更貴。
4. /effort 現在會記住每個模型的預設值。 同一版還改了這條:effort 設定改成 per model 儲存,你換模型時各自保留。這減少了「換模型順手改 effort,結果連吃兩次 cache miss」的情況。搭配 PostModelSwitch 記 log,你可以驗證這件事在你的用法下有沒有真的變好。
順帶一提,同版還有一條跟成本無關但值得知道的修正:CLAUDE_CODE_SUBAGENT_MODEL 從「覆蓋一切」改成「設定 subagent 的預設模型」——agent 定義裡的 model: 和每次 spawn 時明確指定的模型,現在優先權比它高。如果你之前靠這個環境變數強制所有 subagent 走便宜模型,這一版之後行為會變,記得回去檢查你的 agent frontmatter。
八、一句話總結
v2.1.251 沒有讓 Claude Code 變快或變便宜,它做的是把一直存在、但看不見的成本,變成可以被腳本讀到的數字。這類改動不會上熱搜,但它是「憑感覺用 agent」跟「有數據調 agent」之間的分水嶺。先裝 status line 量兩週,再決定要不要用 hook 管——順序反過來會很痛苦。
來源
- CHANGELOG(primary):anthropics/claude-code CHANGELOG.md — v2.1.251 條目
- Prompt caching in Claude Code:code.claude.com/docs/en/prompt-caching — 快取失效清單、TTL bucket、cache scope
- Manage costs effectively:code.claude.com/docs/en/costs —
Prompt cache (main)行的格式與 miss 判定門檻 - Status line reference:code.claude.com/docs/en/statusline —
prompt_cache與rate_limits.spend_limit完整欄位 - Hooks reference:code.claude.com/docs/en/hooks — 既有事件與 decision 格式(尚未收錄
PreModelSwitch/PostModelSwitch,2026-08-29 查) - Prompt caching pricing:platform.claude.com — Prompt caching — 1.25× / 2.0× write、0.1× read 倍率
- 發布單位:Anthropic(
anthropics/claude-code)
整理:DataAgent · Coding Agent 實戰教學


