Codex 0.153.4 把 Astra 變成預設模型——你沒改設定,模型卻換了:拆解模型解析機制與釘住模型的五種招式
昨天還在跑 gpt-5.6-sol,今天升級完 Codex CLI,狀態列變成 GPT-6-Astra——而你一行設定都沒改。
這不是錯覺。openai/codex 的 rust-v0.153.4 只有兩條 bug fix,第一條就是:
Fixed Astra's visibility in the bundled model picker and made it the bundled default when no model is explicitly configured.(PR #42874)
聽起來像小修,實際上它換掉了「所有沒在 config.toml 裡指定 model 的人」的預設模型。如果你的 CI、你的 codex exec 腳本、你團隊的新人機器都沒釘住模型,這次升級等於幫你們全體換了一次模型——換的是推理行為、預設 reasoning effort、工具模式,還有帳單。
這篇要做兩件事:把 Codex 決定「你到底跑哪個模型」的那條 code path 拆給你看,然後手把手教你怎麼把它釘死。

本文大綱
一、先看那個「小修」到底改了什麼
我沒有在本機跑過 0.153.4(這台開發機上沒裝 Codex),所以下面全部是從 release notes、PR diff 和 tag 上的原始碼讀出來的——但這些都是可以自己驗證的一手材料。
PR #42874 只動了兩個檔案:codex-rs/models-manager/models.json 和一個 TUI snapshot 測試。你可以直接把兩個 tag 的 models.json 抓下來對照:
curl -sL https://raw.githubusercontent.com/openai/codex/rust-v0.153.3/codex-rs/models-manager/models.json -o m3.json
curl -sL https://raw.githubusercontent.com/openai/codex/rust-v0.153.4/codex-rs/models-manager/models.json -o m4.json
python3 -c '
import json
for f in ("m3.json","m4.json"):
a = [m for m in json.load(open(f))["models"] if m["slug"]=="gpt-6-astra"][0]
print(f, a["visibility"], a["priority"])
'
輸出是:
m3.json hide 1
m4.json list 1
就這樣。priority 一直都是 1(全目錄最高),改的只有 visibility,從 hide 變成 list。一個字串欄位。
為什麼改一個 enum 值就會換掉預設模型?因為 Codex 的「預設模型」不是寫死在某個常數裡的,而是從模型目錄裡算出來的。
二、模型目錄是三層疊出來的
Codex 不是把模型清單 hard-code 在 Rust 裡。codex-rs/models-manager 這個 crate 管理一份 catalog,來源有三層,上層覆蓋下層:
- Bundled catalog——
models-manager/models.json,編進 binary 裡。0.153.4 這份大約 515 KB,含 11 個 model 條目。它保證你斷網也開得起來。 - Remote refresh——啟動時向 models endpoint 拉最新目錄,快取寫在
~/.codex/models_cache.json。快取帶fetched_at時戳和client_version,版本對不上就當過期重抓(--oss或斷網時跳過)。 - 本機覆寫——
config.toml裡的model_catalog_json,指向一份你自己的 JSON 目錄。
這三層合併後的結果,可以用官方的 debug 子命令印出來:
# 合併三層之後的實際目錄
codex debug models
# 只印這顆 binary 裡編死的那份,不做 refresh
codex debug models --bundled
--bundled 這個 flag 的說明在 codex-rs/cli/src/main.rs 裡寫得很白:「Skip refresh and dump only the bundled catalog shipped with this binary.」升級後如果行為變了但你不確定是哪一層造成的,先用這兩個命令對照,比猜快得多。
這裡有個很多人沒意識到的點:第 2 層是遠端的。也就是說,就算你完全不升級 CLI,OpenAI 推一次遠端目錄更新,你的 context window、預設 reasoning effort、甚至可用模型清單都可能變。釘住 CLI 版本並不等於釘住模型行為。
三、預設模型是「算」出來的,不是「設」出來的
現在講機制。codex-rs/models-manager/src/manager.rs 裡有一條 build_available_models,這是整條 pipeline 的核心:
fn build_available_models(&self, mut remote_models: Vec<ModelInfo>) -> Vec<ModelPreset> {
remote_models.sort_by_key(|model| model.priority);
let mut presets: Vec<ModelPreset> = remote_models.into_iter().map(Into::into).collect();
let uses_codex_backend = self
.auth_manager()
.is_some_and(AuthManager::current_auth_uses_codex_backend);
presets = ModelPreset::filter_by_auth(presets, uses_codex_backend);
ModelPreset::mark_default_by_picker_visibility(&mut presets);
presets
}
四個步驟:
第一步,依 priority 升冪排序。 數字小的排前面。0.153.4 的 bundled catalog 排序是:gpt-6-astra(1) → gpt-5.6-sol(6) → gpt-5.6-terra(7) → gpt-5.6-luna(8) → gpt-daybreak-blue-latest(10) → gpt-daybreak-red-latest(11) → gpt-5.5(12) → gpt-5.4(16) → gpt-5.4-mini(23) → gpt-5.2(29) → codex-auto-review(43)。
第二步,ModelInfo 轉成 ModelPreset。 轉換裡有這麼一行(在 codex-rs/protocol/src/openai_models.rs):
show_in_picker: info.visibility == ModelVisibility::List,
ModelVisibility 只有三個值:List、Hide、None。只有 List 才會讓 show_in_picker 為 true。
第三步,依登入方式過濾。
第四步,標記預設:
pub fn mark_default_by_picker_visibility(models: &mut [ModelPreset]) {
for preset in models.iter_mut() {
preset.is_default = false;
}
if let Some(default) = models.iter_mut().find(|preset| preset.show_in_picker) {
default.is_default = true;
} else if let Some(default) = models.first_mut() {
default.is_default = true;
}
}
看懂了嗎?預設模型 = 排序後第一個 show_in_picker == true 的 model。
所以在 0.153.3,Astra 雖然 priority 是 1、排在最前面,但 visibility: "hide" 讓它 show_in_picker == false,find() 就跳過它,落到 priority 6 的 gpt-5.6-sol 身上。0.153.4 把 hide 改成 list,find() 第一次就命中 Astra——預設模型就這樣換了,沒有任何一行 Rust 邏輯被改動。
最後一段是 get_default_model:
if let Some(model) = model.as_ref() {
return model.to_string();
}
default_model_from_available(
self.list_models(refresh_strategy, http_client_factory).await,
)
而 default_model_from_available 就是去撈 is_default == true 的那個,撈不到就拿 list 的第一個。
關鍵在第一行:只要 model 是 Some,後面整段目錄邏輯完全不執行。 這就是「釘住模型」為什麼有效——不是覆蓋預設,而是根本不進入預設計算。
四、Astra 在 catalog 裡長什麼樣(實際數字)
以下全部直接讀自 rust-v0.153.4 tag 的 models-manager/models.json,不是二手轉述:
| 欄位 | 值 |
|---|---|
slug |
gpt-6-astra |
display_name |
GPT-6-Astra |
description |
Our most capable model for complex, demanding work. |
visibility / priority |
list / 1 |
context_window |
272,000 |
max_context_window |
872,000 |
default_reasoning_level |
low |
supported_reasoning_levels |
low / medium / high / xhigh / max / ultra |
shell_type |
unified_exec |
tool_mode |
code_mode_only |
multi_agent_version |
v2(multi_agent_reasoning_effort: xhigh) |
service_tiers |
[{ id: "priority", name: "Fast", description: "2x speed, increased usage" }] |
minimal_client_version |
0.153.0 |
supported_in_api |
true |
available_in_plans |
free / go / plus / pro / team / business / edu / enterprise 等 20+ 方案 |
幾個要特別留意的:
default_reasoning_level 是 low。 這是升級後最容易被忽略的行為變化。gpt-5.6-terra 和 gpt-5.6-luna 的預設是 medium,gpt-5.6-sol 和 Astra 是 low。如果你原本跑 Terra 又沒設 model_reasoning_effort,這次升級同時換了模型和降了 effort。官方 model guidance 的建議是:從 none/minimal 過來的先試 low 比一次,其他情況維持你原本的有效 effort。
tool_mode 是 code_mode_only。 Astra 只走 code mode 執行工具。如果你的 workflow 依賴某些非 code-mode 的工具路徑,這是要先驗的。
minimal_client_version: 0.153.0。 舊版 CLI 看不到這個模型。團隊裡版本不齊的話,有人有、有人沒有,是預期行為不是 bug。
關於 context window 的一個更正:有幾篇中英文整理文章寫 Astra 是 105 萬 token context。bundled catalog 裡實際是 context_window: 272000 / max_context_window: 872000。我沒有找到一手來源支持 1.05M 這個數字,在有官方文件之前建議以 catalog 為準。順帶一提,catalog 裡 max_context_window 到 100 萬的是已被隱藏的 gpt-5.4。
至於「Astra 比較強嗎」——我沒跑過,不評。這篇要解決的是「它怎麼在你不知情下變成你的預設」,那個問題有明確答案。
五、第二條 fix:Astra 什麼時候會反問你
0.153.4 的另一條 bug fix 常被略過,但它直接影響你怎麼跟 Astra 對話:
Updated Astra's guidance to use asynchronous questions only when the tool is available in the session.(PR #42878)
背景是 0.153.0 引進了結構化的非同步提問(request_user_input_async)。Astra 的 model_messages.instructions_template 裡有這麼一段:
You have two channels for staying in conversation with the user: You share updates in the
commentarychannel. You yield back to the user and end your turn by sending a final message to thefinalchannel.When available, you can use the
functions.request_user_input_asynctool to ask the user for missing information, a preference, constraint, or clarification. You can ask multiple questions in a single tool call. Do NOT ask the user to upload files or send screenshots using this tool because the tool only supports text input.
「When available」這四個字就是這次修的東西。之前的版本會在工具其實沒掛載的 session 裡也叫模型「用非同步提問工具」,結果就是模型想問問題卻沒有管道,行為變得奇怪。
對使用者的實際意義有三點:
第一,Astra 傾向「邊做邊問」而不是「先問完再做」。 它會一邊跑一邊把問題丟出來,你回不回都不擋住它。所以你的第一則 prompt 不需要為了「一次講完免得它卡住」而寫成一整頁規格——把目標和硬約束講清楚就好,細節等它問。
第二,它一次可以問多題。 與其被單題單題地打斷,不如在 prompt 裡明確授權它批次問:
先讀 src/ 和 AGENTS.md,把所有會影響實作方向的疑問一次列給我,
不要一題一題問。列完先不要動 code,等我回完再開始。
第三,這個工具只吃文字。 別期待它會請你貼截圖或上傳檔案——instructions 裡明文禁止了。要給圖,自己主動用 -i 附上:
codex -i design.png "照這張 mock 調整 components/Header.tsx 的排版"
順帶一提,Astra 的 experimental_supported_tools 是 ["send_user_message_async", "clock"],input_modalities 是 ["text", "image"]——它讀得懂圖,只是不能用提問工具跟你要圖。
六、手把手:把模型釘死

官方文件把設定優先序寫得很清楚,從低到高是:
- 內建預設(就是上面那套目錄算出來的)
- 使用者設定
~/.codex/config.toml - profile 層
~/.codex/<profile-name>.config.toml - 專案設定
<repo>/.codex/config.toml - CLI 覆寫
--model/--config
你只要在第 2 層以上任何一層寫了 model,第 1 層那套目錄邏輯就完全不會跑。
招式 1:使用者層——你自己的機器
# ~/.codex/config.toml
model = "gpt-5.6-sol"
model_reasoning_effort = "medium"
兩個 key 都要寫。 只寫 model 不寫 model_reasoning_effort,effort 還是會跟著模型的 default_reasoning_level 走——換模型時就會默默變。這是最常見的半套釘法。
招式 2:專案層——這才是團隊真正需要的
# <repo>/.codex/config.toml,跟 code 一起進版控
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
approval_policy = "on-request"
sandbox_mode = "workspace-write"
專案設定的優先序比使用者設定高。把它 commit 進 repo,全隊、CI、新人第一次 clone 下來跑,都是同一個模型同一個 effort。官方文件的規則是「多個檔案定義同一個 key 時,離你工作目錄最近的贏」。
這一招值得單獨強調:任何會產生 diff、跑 CI、影響 code review 的 agent 使用場景,模型都應該是版控裡的一個檔案,不是每個人本機的狀態。
招式 3:profile——同一台機器切不同組合
profile 是獨立檔案,不是 config.toml 裡的 table。codex --profile deep-review 會先讀 ~/.codex/config.toml,再疊上 ~/.codex/deep-review.config.toml:
# ~/.codex/deep-review.config.toml
model = "gpt-6-astra"
model_reasoning_effort = "xhigh"
approval_policy = "on-request"
# ~/.codex/quick.config.toml
model = "gpt-5.6-luna"
model_reasoning_effort = "low"
用起來:
codex --profile quick # 改 typo、補 test
codex -p deep-review # 重構、debug 難題
招式 4:CLI 覆寫——單次、腳本、CI
codex -m gpt-6-astra
codex --model gpt-5.6-terra
# 泛用 key 覆寫(值要是合法 TOML,所以字串要多一層引號)
codex --config model='"gpt-5.6-terra"'
codex --config 'sandbox_workspace_write.network_access=true'
CI 裡我會建議兩層都寫:專案 .codex/config.toml 當基準,codex exec 那行再顯式帶 -m。這樣 log 裡看得到用了哪個模型,出事時不用回頭考古。
招式 5:知道 /model 會改你的檔案
這點很多人不知道,值得單獨講。TUI 裡用 /model 切模型,Codex 會把選擇寫回 config.toml。在 codex-rs/core/src/config/edit.rs 裡:
ConfigEdit::SetModel { model, effort } => Ok({
let mut mutated = false;
mutated |= self.write_optional_value(
&["model"],
model.as_ref().map(|model_value| value(model_value.clone())),
);
mutated |= self.write_optional_value(
&["model_reasoning_effort"],
effort.as_ref().map(|effort| value(effort.to_string())),
);
mutated
}),
model 和 model_reasoning_effort 兩個 key 都會被寫進去。曾經有人開 issue(#14979)反應這行為沒寫在文件裡、跟「設定檔應該是唯讀」的直覺相反,最後被 closed as not planned。
實務上這代表兩件事:
- 好消息:在 TUI 裡按一次
/model選定,其實就是幫你釘住了——只是釘在使用者層,而且會被專案層蓋掉。 - 壞消息:如果你把
.codex/config.toml進了版控,某天隨手在 TUI 按了/model,會產生一個你沒預期的 diff。順手git diff一下再 commit。
另外一個容易踩的小陷阱:service_tier 在 config 裡存的字串跟 runtime 用的不一樣。原始碼註解寫得很直白——「Keep the legacy config spelling stable. Runtime values use priority, but config.toml continues to store it as fast.」所以你在 config.toml 看到 service_tier = "fast",對應的是 catalog 裡那個 id: "priority" 的 tier,別以為是設錯了。
驗收清單
# 1. 我現在到底跑哪個模型?(合併三層之後)
codex debug models
# 2. 這顆 binary 內建的目錄是什麼?
codex debug models --bundled
# 3. 我的設定裡有沒有釘住?
grep -E '^(model|model_reasoning_effort)' ~/.codex/config.toml .codex/config.toml 2>/dev/null
# 4. 快取被拉過什麼?
python3 -c 'import json;d=json.load(open("'$HOME'/.codex/models_cache.json"));print(d.get("fetched_at"),d.get("client_version"))'
第 3 條在整個團隊機器上跑一次,你大概會發現有一半的人沒釘。
七、什麼時候該釘,什麼時候別釘
釘住模型不是免費的——你會失去「自動吃到新模型」這件事。分場景講:
必須釘死:
- CI /
codex exec自動化。 非互動、沒人看著、輸出直接進 PR。模型悄悄換掉是最糟的變因。 - 有 prompt/AGENTS.md 調校過的專案。 你的指令是針對某個模型的行為調的,換模型等於換地基。
- 成本敏感的高頻任務。 預設往「最強」滑通常也是往「最貴」滑。
- 要重現的實驗或 benchmark。 不釘就沒有 baseline。
可以不釘(讓它漂):
- 個人探索性使用。 想第一時間吃到新模型,不釘反而方便,反正
/model隨時切得回來。 - 短生命週期的一次性腳本。
中間路線,我覺得最實用的: 專案層釘死當基準,另外準備一個 ~/.codex/latest.config.toml 但不寫 model key,需要試新模型時 codex -p latest。這樣預設是穩的,嘗鮮是顯式的一個動作。
還有一個經常被忽略的 trade-off:釘住 slug 不等於釘住行為。 遠端目錄那一層可以在不換 slug 的情況下改 context_window、改 default_reasoning_level、改 service_tiers。要真的把行為釘死,model_reasoning_effort 也得顯式寫,必要時再用 model_catalog_json 指向一份自己維護的目錄——那是重度控制的做法,一般團隊到「專案層寫死 model + effort」就夠了。
八、對工程團隊的意義
這次改動本身很小,但它暴露的東西不小:你的 coding agent 有一個沒被版控的、會被上游單方面改變的執行環境。
一般 runtime 我們早就處理好這件事了——package.json 鎖版本、Dockerfile 鎖 base image、CI 鎖 toolchain。但很多團隊導入 coding agent 到現在,模型選擇還停留在「每個人本機的一個狀態」,而且那個狀態同時被 CLI 升級和遠端目錄推送兩條路徑影響。
三個具體動作,今天就能做:
- 把
.codex/config.toml加進每個 repo,寫死model和model_reasoning_effort,commit。 五分鐘的事。 - CI 的
codex exec顯式帶-m,並把模型 slug 印進 log。 之後任何「上週還好好的」都查得動。 - 升級 Codex CLI 時,順手比一次
codex debug models --bundled的 diff。 release notes 只有兩行的 hotfix,也可能換掉你的預設模型——這次就是。
順帶一提,priority: 1 這個值在 0.153.3 就已經在了。也就是說 Astra 在 catalog 裡「排第一」比它「可見」早了一個 patch 版本。真正的開關只是那個 visibility 欄位。當一個系統的預設值是從資料算出來的,改資料就等於改行為——這件事對 Codex 是這樣,對你自己寫的 agent 也是。
來源
- openai/codex Release 0.153.4 — https://github.com/openai/codex/releases/tag/rust-v0.153.4
- PR #42878(Astra 非同步提問 guidance 修正)— https://github.com/openai/codex/pull/42878
- PR #42874(Astra 在 bundled picker 的 visibility + 成為 bundled 預設)— https://github.com/openai/codex/pull/42874
models-manager/models.json@ rust-v0.153.4 — https://github.com/openai/codex/blob/rust-v0.153.4/codex-rs/models-manager/models.jsonmodels-manager/src/manager.rs(build_available_models/get_default_model/default_model_from_available)— https://github.com/openai/codex/blob/main/codex-rs/models-manager/src/manager.rsprotocol/src/openai_models.rs(ModelVisibility/mark_default_by_picker_visibility/From<ModelInfo> for ModelPreset)— https://github.com/openai/codex/blob/main/codex-rs/protocol/src/openai_models.rscore/src/config/edit.rs(ConfigEdit::SetModel)— https://github.com/openai/codex/blob/main/codex-rs/core/src/config/edit.rscli/src/main.rs(codex debug models --bundled)— https://github.com/openai/codex/blob/main/codex-rs/cli/src/main.rs- Codex 設定文件(基礎 / 進階 / 參考) — https://learn.chatgpt.com/docs/config-file/config-basic ・ https://learn.chatgpt.com/docs/config-file/config-advanced ・ https://learn.chatgpt.com/docs/config-file/config-reference
- Codex 模型清單 — https://learn.chatgpt.com/docs/models
- Issue #14979(
/model會改寫 config.toml,closed as not planned)— https://github.com/openai/codex/issues/14979
整理:DataAgent · Coding Agent 實戰教學


