Hub Codex CLI 完整教學

第 5 篇 大師 · 第 15 章

MCP 深度整合、安全強化與效能成本調校

篇導讀(大師篇末章)

適合對象:已經會用 MCP、會調 config.toml、懂三大 Approval/Sandbox preset,現在想把 Codex CLI 當「正式生產工具」用的高手。

閱讀方式:這章是「組合技 + 縱深防禦 + 省錢省 token」三合一,每節都先一句白話講「為什麼你會需要它」,再給可貼用的設定。沒碰過 MCP 的基礎請先回第 13 章;沒碰過 Sandbox 的請先回第 4 章

本章做完你會:幫每個 MCP server 精準控管權限、token 不寫入磁碟、把 Codex 自己變成 MCP server 給別的工具驅動、用 [permissions] 寫出「可寫程式碼但連 .env 都不准讀」的鐵桶 profile、看懂沙箱底層怎麼把 AI 關進籠子、用 profile overlay 檔做「一任務一人格」的成本路由,以及把每一回合的 token 當錢看。

涵蓋:15.1~15.14 共 14 節。

重要提醒

Codex CLI 數天就一個新版(本章寫作時最新是 0.140.0 / 2026-06-15),旗標、config 鍵、feature flag 隨時會改。本章每個事實都標了信心等級,凡是標「實驗性」「社群」「以實機為準」的,請務必用你電腦上的 codex --helpcodex doctorcodex features list 親自確認一次再照做,不要照抄寫死進自動化腳本。

15.1 MCP client 進階鍵:env、過濾順序、Plugin 覆寫、OAuth 綁定

這節一句話重點:每個 MCP server 都吃你的 token 和 context,進階鍵讓你「給得剛剛好」——值不寫入磁碟、工具只露需要的、權限逐項管。

基礎的 [mcp_servers.名稱] 怎麼寫,見第 13 章。這裡只補高手才會碰的幾個鍵。

env vs env_vars:兩個長很像、語意完全不同的鍵

很多人把這兩個搞混,結果不是 token 明文寫入磁碟,就是環境變數沒傳進去:

型別它在做什麼一句話
envmap<string,string>設值:直接寫死 key=value 注入 server 程序「我幫你準備一個值」
env_varsarray<string | {name, source}>白名單轉發:放行某個「你電腦上已經有」的環境變數,值不寫進檔「你自己有的我放行,不抄一份」

官方逐字:env 是「Environment variables forwarded to the MCP stdio server」;env_vars 是「Additional environment variables to whitelist for an MCP stdio server」。

token 不寫入磁碟的正確姿勢

stdio server 要傳 token,env_vars = ["MY_TOKEN"](白名單轉發,值留在你環境裡、不進 config.toml),不要env = { MY_TOKEN = "sk-..." }(那等於把金鑰明文寫進設定檔)。

env_vars 還有進階寫法——可以指定從哪個環境取值:

[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
env_vars = ["LOCAL_TOKEN"]                       # 從你本機環境讀(預設 source = "local")
# 或物件形式,從遠端 executor 環境讀:
# env_vars = [{ name = "TOKEN", source = "remote" }]

物件形式 { name = "TOKEN", source = "remote" } 要從遠端 executor 環境讀,前提是搭配 experimental_environment = "remote"(此鍵讓 stdio server 透過遠端 executor 啟動)。

重要提醒

source = "remote" 只對 stdio server 生效。官方文件只描述 remote 對 stdio 啟動有效,streamable HTTP server 的 remote placement 官方未見支援敘述——不要假設 HTTP server 能 remote。以實機 config-reference 為準。

逾時與必要性:startup_timeoutrequired

預設何時調
startup_timeout_sec10 秒npx -y 冷啟動、Windows Defender 即時掃描拖慢 → 提到 15~60 秒
startup_timeout_ms(毫秒別名)官方逐字「Alias for startup_timeout_sec in milliseconds」,要毫秒精度時用
tool_timeout_sec60 秒長跑工具(爬蟲、大檔)→ 視需要上調,避免誤判逾時
required(未設即非必要)true:這個 server 啟不起來就讓整個 session 失敗(CI 場景,別讓「少一個 server」靜默過關)

工具過濾的求值順序(組合技關鍵)

當你同時用白名單 enabled_tools 和黑名單 disabled_tools,順序很重要——黑名單後於白名單套用

[mcp_servers.chrome_devtools]
enabled_tools  = ["open", "screenshot"]   # 1. 先白名單:只露這兩個
disabled_tools = ["screenshot"]           # 2. 再黑名單:screenshot 被剔除
# 最終:只剩 open

這是官方文件自己的範例。求值順序完整版:enabled_tools(白名單)→ disabled_tools(黑名單,後套用)→ default_tools_approval_mode(server 級核准)→ tools.<tool>.approval_mode(單一工具覆寫)。核准模式三個值:auto / prompt / approve

想要「查詢隨便跑、寫入才擋」,不用整台逐一核可

很多人拿到一個工具很多的 server,第一反應是把整台設成逐一核可,結果每個唯讀查詢都要按一次同意,體驗很差。其實不用這麼粗暴:把常用的唯讀工具留在 auto,只針對會寫入/刪除/部署的工具個別設 tools.<tool>.approval_mode = "approve",就能做到「查詢類直接跑、寫入類才停下來問」。部分社群資料另外提到 default_tools_approval_mode 有一個介於 autoapprove 之間、名為 writes 的整台捷徑值,但本書目前查到官方頁面明列的仍是 auto/prompt/approve 三個——這個捷徑值存不存在、確切拼法,請跑 codex mcp add --help 或查最新 config-reference 核對,不確定就用上面「逐工具覆寫」的笨方法,效果一樣。

destructive 標註蓋過你所有的核准設定

官方明文規則:只要一個 MCP 工具在自己的描述裡標了 destructive 提示,不論你把 approval_policydefault_tools_approval_mode 設成什麼,一律強制觸發核准——就算同一個工具同時也標了 read-only 提示也一樣,destructive 優先。這也是挑 MCP server 的一個實務判準:老實標好 destructive 的 server,天生比沒標註的更安全,因為 Codex 會替你多擋一道,不管你自己的核准設定有沒有漏配。

Plugin-scoped 覆寫:不改 plugin 本體也能調它的 MCP

如果某個 plugin 內建了 MCP server,你可以用獨立命名空間覆寫它,而不去動 plugin 本身:

[plugins."sample@test".mcp_servers.sample]
enabled = true
default_tools_approval_mode = "prompt"
enabled_tools = ["read", "search"]

[plugins."sample@test".mcp_servers.sample.tools.search]
approval_mode = "approve"

可覆寫的鍵:enabled / default_tools_approval_mode / enabled_tools / disabled_tools / tools.<tool>.approval_mode

streamable HTTP 專屬欄位:跟 stdio 不是同一組鍵

前面 env / env_vars 講的都是 stdio server(本機子程序)。如果你接的是 streamable HTTP server(codex mcp add --url ... 那種),設定檔裡對應的是另一組欄位:

欄位作用
urlserver 的 endpoint 網址
auth認證方式,例如 "oauth"
bearer_token_env_varbearer token 從哪個環境變數讀(同 env_vars 的精神:值不寫死進檔)
http_headers固定附帶的額外 HTTP header
env_http_headersheader 值改從環境變數讀,白名單轉發、不寫入磁碟
oauth_resource選填,依 RFC 8707 標明 OAuth resource indicator
scopesOAuth 要求的權限範圍
[mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
auth = "oauth"
scopes = ["repo", "user"]

重要提醒

MCP 這塊的欄位名稱改得比其他設定快,上表整理自多方資料而非逐字截自單一頁面。動筆寫自動化腳本前,務必先跑一次 codex mcp --help 或翻最新 config-reference 核對拼法,尤其 oauth_resource 這類較新欄位。

OAuth callback 綁定(冷門安全細節)

連 OAuth 型的 HTTP server 時,callback 埠的綁定行為會影響暴露面:

作用
mcp_oauth_callback_port固定本機 callback 埠(未設則用隨機埠)
mcp_oauth_callback_url自訂 redirect URI(devbox / ingress 用)
mcp_oauth_credentials_storeauto(預設)/ file / keyring

重要提醒

官方逐字「Local callback URLs bind on the local interface; non-local callback URLs bind on 0.0.0.0」。意思是 callback URL 若是 localhost 就綁本機介面(安全);若設成非 local 的 URL,Codex 會綁 0.0.0.0——暴露面變大,devbox 場景務必配防火牆。

另外,自建 server 時別忘了 server 在 init 回傳的 instructions 欄位會被 Codex 讀來輔助選工具,官方建議前 512 字元要 self-contained

官方參考

MCPConfiguration Reference

15.2 MCP CLI 與把 Codex 變成 MCP server

這節一句話重點:codex mcp add 幫你快速接 server(但沒有 --scope),而 codex mcp-server 能反過來把 Codex 自己變成別人能驅動的 MCP server——前提是你的 client 一定要接住 approval handler,否則它會卡死等核准。

codex mcp add 旗標(注意:沒有 --scope

旗標說明
--env KEY=VALUE(可重複)stdio server 環境變數
--url https://…註冊 HTTP server(與 command 互斥)
--bearer-token-env-var ENV_VARHTTP server 的 token 來源環境變數名
--oauth-client-id / --oauth-resourceHTTP server OAuth 參數
-- <COMMAND...>stdio 啟動指令(置於 -- 之後)
# 加一個 stdio server
codex mcp add context7 -- npx -y @upstash/context7-mcp

# 只傳非敏感設定,例如日誌等級
codex mcp add myserver --env LOG_LEVEL=info -- my-server-cmd --flag

--env KEY=VALUE 不要填金鑰

codex mcp add 會把 server 設定持久寫入 ~/.codex/config.toml--env KEY=VALUE 只適合非敏感設定;不要填 API key、token 或密碼。憑證應由系統憑證庫、CI secret 或啟動 Codex 前已安全注入的環境變數提供;server 設定只轉交變數名稱,例如 env_vars = ["SERVICE_API_KEY"]

重要提醒

Codex 的 codex mcp add 沒有 --scope global|project 旗標(那是 Claude Code / Gemini CLI 的東西)。社群提案的 --scoped/-s 至今未實作。要寫專案層的 .codex/config.toml 目前只能手動編檔,而且該專案必須是「信任專案」才會載入(見 15.10)。

不改檔、單次 session 臨時覆寫任何 MCP 鍵,用全域 -c / --config

# 臨時停用某 server
codex --config mcp_servers.context7.enabled=false

# 臨時拉長啟動逾時(值含特殊字記得用引號)
codex -c "mcp_servers.github.startup_timeout_sec=60"

# 結構化盤點 / 單點診斷
codex mcp list --json          # 機器可讀,含 marketplace 資訊
codex mcp get context7 --json  # 單一 server 的 raw config(診斷首選)

把 Codex 變成 MCP server

# 把 Codex 當 server 接到你的 client
codex mcp-server | your_mcp_client

# 用官方 Inspector 看可用方法(自建除錯首選)
npx @modelcontextprotocol/inspector codex mcp-server

指令名是 codex mcp-server(連字號),沒有 codex mcp serve。它走 stdio 的 JSON-RPC 2.0、line-delimited,downstream client 關連線時自己退出,並且會繼承全域設定覆寫。它也吃這些全域旗標:-c/--config-p/--profile-m/--model--strict-config

# 自建配方:用專屬 profile 驅動 + 設定打錯立即失敗
codex mcp-server -p ci-profile --strict-config

Approval handler 必接(否則 hang)

當你寫 client 去驅動 Codex server,Codex 會在要改檔 / 跑指令前丟「核准請求」給你,你必須回覆 {decision},否則它會卡住等核准

applyPatchApproval   { conversationId, callId, fileChanges, reason?, grantRoot? }
execCommandApproval  { conversationId, callId, approvalId?, command, cwd, reason? }
# Client 必須回:{ decision: "allow" | "deny" }

重要提醒

自建 client 沒實作這兩個 approval handler,是 codex mcp-server 整個 hang 死的頭號原因(官方 issue #6664)。另外官方明文標這套介面「experimental and subject to change without notice」——方法名、欄位、event 都可能改,自建整合一定要釘住 Codex 版本

更隱蔽的一種卡死:有接 handler,但呼叫時沒帶核准參數

#6664 是「完全沒實作 approval handler」的情況。另有一種狀況是 downstream client 不支援 elicitation 協定,碰到需要核准的指令就可能卡住。優先實作 approval handler,或在不支援時明確失敗/逾時;不要為了不掛住而關掉核可與沙箱。若相容性測試真的必須暫時使用最大授權,僅限無憑證、無正式資料、可丟棄的隔離環境,並在呼叫端限制任務與可寫路徑,不能作為一般 client 的預設。

事後稽核:從 rollout JSONL 回放工具呼叫

不管是 Codex 自己呼叫 MCP 工具、還是別人透過 codex mcp-server 驅動 Codex,每一輪都會忠實記錄進 session JSONL(~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl,同一份檔案 15.14 會用來查 token 用量)。事後想知道某次自動化到底呼叫了哪些工具、帶了什麼參數、回傳了什麼,不用憑印象回想,直接查檔案:

jq 'select(.type == "tool_call")' ~/.codex/sessions/2026/07/10/rollout-*.jsonl

先把 rollout JSONL 視同機密紀錄。它可能含 prompt、工具參數、MCP 回應與路徑資訊;只在本機、權限受限的位置做最小範圍查詢,不要直接上傳、貼工單或 commit 原始檔。需要分享時,先篩選必要欄位,再手動遮罩憑證、個資與內網資訊。

這比事後憑記憶回報「那次自動化到底改了什麼」可靠得多,成本回顧、安全事件回顧都用得上。

15.3 MCP 除錯:段名硬規則、log、失敗模式表

這節一句話重點:MCP 連不上九成是三件事——段名打成連字號、握手逾時、ERROR 其實是假警報;先看 /mcp,再翻 log,別被 log 嚇到。

段名底線硬規則(最常見的靜默失敗)

[mcp_servers.myserver]   # ✅ 正確:底線
# [mcp-servers.myserver] # ❌ 連字號 → 靜默忽略,不報錯!
# [mcpServers.myserver]  # ❌ 駝峰 → 同樣靜默忽略

重要提醒

段名一定要 [mcp_servers.名稱](底線)。寫成連字號 [mcp-servers] 或駝峰 [mcpServers],Codex 會靜默忽略整段、完全不報錯,你的 server 就像沒設一樣。這是官方 issue #3441 的實證坑。

結構化診斷(官方優先)

# TUI 內:看 active server / transport / 已註冊工具
/mcp

# 單一 server 的 raw config(設定到底有沒有被讀到)
codex mcp get <server-name> --json

RUST_LOG 深度 trace 與 log 路徑

Codex 是 Rust 程式,可以用 RUST_LOG 控制分層 trace:

# 連不上 / 逾時時,先全開再縮
RUST_LOG=codex_core=trace codex

更精準的 module target(如 codex_core::mcp_connection_manager=trace)和 log 檔路徑 ~/.codex/log/codex-tui.log,出自第三方知識庫。

重要提醒

codex_core::mcp_connection_manager 這個精確 module 路徑、~/.codex/log/codex-tui.log 這個 log 檔路徑、以及 /mcp verbose(展開 schema),官方頁都未逐字列出,屬第三方來源。~/.codex/ 本身是官方確認的設定根目錄,但確切 module/路徑名請以實機為準(可先 RUST_LOG=codex_core=trace 全開再縮範圍)。

已知失敗模式速查表

症狀根因 / 解法
handshaking with MCP server failed: connection closed多 server(9+)同時全失敗(issue #6020,未結);逐一 enabled = false 縮小,檢查 OAuth / 網路 / 逾時
全 server silent timeoutWindows stdio wiring 缺陷(initialize 沒送進 stdin)
log 把成功啟動記成 ERROR0.42.0 的 log 噪音(issue #4590)——別被 ERROR 字樣嚇到,看 /mcp 工具有沒有真的出現
config 裡的 MCP 完全不生效段名打成連字號(見上,issue #3441)
npx -y 在 Windows 握手逾時Defender 掃描 + npm 下載拖慢 → 提高 startup_timeout_sec(15~60)
自建 server 被驅動時整個 hangclient 沒接 approval handler(見 15.2)
codex exec 下 MCP 工具呼叫被靜默 cancelled非互動模式 stdin 已關閉,命中預設/on-request 核准政策就自動取消(issue #24135,撰稿時仍開放);見下方說明
已核准過、甚至 approval_policy="never" 的 MCP 工具仍反覆跳出核准提示已知 bug(issue #19430),會打斷原本設計成全自動的委派流程,目前無 config 層修法
Windows 原生 PowerShell 下 npx 型 stdio server「Request timed out」疑似命令列引號處理在 stdio handshake 階段出包;改用預先裝好的 .cmd shim 或 8.3 短路徑,別直接呼叫 npx
VS Code 擴充套件 / Desktop app 指向 WSL 時,CLI 裡設定好的 MCP server 偵測不到多筆 issue 並存、非單一根因(#13690、#25280、#21470、#29193);CLI 本身沒事,問題出在 client 端的 WSL 探索邏輯

codex exec + MCP 組合的已知硬傷

把 MCP server 接進 codex exec 這種非互動流程(第 10 章)要特別小心:非互動模式下 stdin 已經關閉,MCP 工具呼叫只要命中預設或 on-request 核准政策,就會被自動取消(cancelled)而不是卡住等你回答——結果通常是那一步任務悄悄失敗,畫面上不見得有醒目的錯誤。撰稿時沒有一個乾淨的設定鍵能「只放行 MCP 工具、其餘沙箱限制照舊」;已知唯一逃生門是 15.8 會講的 --dangerously-bypass-approvals-and-sandbox,但那連 read-only 沙箱限制都一併拆掉,不是精準手術。CI 裡要用 MCP,務必替該 server 設 required = false,並實測整條流程不會因此靜默半途而廢。

0.140.0 的 MCP 可靠性改進

官方 changelog 逐字:「Improved MCP reliability by retrying transient startup failures, reporting unusable OAuth credentials as logged out, and preserving explicitly disabled servers.」

意思是:(1) 暫時性啟動失敗會自動重試;(2) OAuth 憑證失效會被當成「已登出」而非硬錯(去重登即可);(3) 你手動標為 disabled 的 server 不會被自動拉回來。

重要提醒

上面第三點官方原文是 explicitly disabled servers(手動停用),不是「disconnected(斷線)」——別記成斷線會被保留。

官方參考

changelogissue #3441

15.4 MCP 效能與成本觀測:砍 schema、OTEL 指標

這節一句話重點:每個 MCP server、每個工具的 schema 都在你還沒打字前就吃掉 context;enabled_tools 是最直接的省錢動作,OTEL 則讓你量到底花了多少。

每個工具的 schema 都是成本

MCP server 越多、工具越多,system prompt 就越膨脹,不只貴,還更容易選錯工具。最直接的對策是用 enabled_tools 只暴露你真的會用的工具,砍掉 schema 噪音:

[mcp_servers.github]
enabled_tools = ["create_pull_request", "list_issues", "get_file_contents"]

重型 server 平時用 enabled = false 關著,要用時再 codex -c mcp_servers.<id>.enabled=true 臨時開。

省 token 直覺

一個工具動輒幾百到上千 token 的 schema 開銷(社群量測,實際依版本變),93 個工具的大型 GitHub MCP server 光 schema 就可能吃掉上萬 token / 回合——在你輸入任何字之前。基本 Git 操作用 Codex 內建工具,通常遠比掛一個 93-tool 的 MCP server 便宜。詳細量級見 15.14。

OTEL MCP 指標

啟用 [otel] 區塊後,Codex 對 MCP 工具呼叫會發 metrics:

  • codex.mcp.call(counter,計數)
  • codex.mcp.call.duration_ms(histogram,耗時)

status 屬性;正常工具呼叫另含 tool,可用時含 connector_id / connector_name

重要提醒

histogram 的指標名是 codex.mcp.call.duration_ms(前面有 codex. 前綴,和 counter 同前綴)。別寫成 mcp.call.duration_ms——漏了前綴,你的 grep 或 dashboard 就找不到指標。

已知缺口

codex exec 不發 OTel metrics;codex mcp-server 完全不發 OTel telemetry(issue #12913)。所以自建 server 場景無法靠 OTEL 觀測 MCP 呼叫,只能回去看 15.3 的 log。

15.5 [permissions] named profile:超越 sandbox_mode 的細粒度權限

這節一句話重點:這是當前進階/企業部署的權限主軸——它能做到舊 sandbox_mode 做不到的事:「全 workspace 可寫,但連 .env 都不准讀」,代價是兩套系統不可混用。

入門教的 sandbox_mode + [sandbox_workspace_write] 仍可用(見第 4 章),但官方已推出更強的 [permissions] 命名 profile 系統。

三個內建 profile

Profile官方語意
:read-only「keeps local command execution read-only」(本機指令唯讀)
:workspace「allows writes inside the active workspace roots and system temp directories」(可寫 workspace 與系統 temp)
:danger-full-access「removes local sandbox restrictions」(拆掉本機沙箱限制,只在刻意需要時用)

自訂 profile + filesystem 三值

default_permissions = "project-edit"        # 選定預設 profile

[permissions.project-edit]
description = "Project editing, deny all secrets."
extends = ":workspace"                       # 可繼承 :read-only / :workspace / 其他具名 profile

filesystem 三個存取等級:

行為(官方逐字)
read可讀檔、列目錄
write可讀、可改(含建立、改名、刪除)
deny讀寫都拒

優先序鐵則(官方逐字):「more specific entries override broader entries」,且同路徑下 deny > write > read 這就是「可寫 source 但鎖死 .env」的關鍵——精確的 deny 蓋過廣泛的 write

[permissions.project-edit.filesystem]
glob_scan_max_depth = 3                       # Linux/WSL/Windows 無界 ** 需要 bounded 預展開

[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"                                 # workspace 整個可寫
"**/*.env" = "deny"                           # 但 .env 連讀都不准

支援的 path token::root(檔系根)、:minimal(runtime 必要路徑)、:workspace_roots(當前 + profile 定義的 roots)、:tmpdir$TMPDIR)、:slash_tmp/tmp)、絕對路徑、~/path。subpath 相對於每個 workspace root 解析,../ parent traversal 被拒。

小技巧

deny glob 適合做 deny-read 規則(較可靠);read / write 的 glob 官方說「less portable」(跨平台較不穩),保護機密優先用 deny

互斥鐵律(最重要)

官方明文「Use one system or the other for a session, not both.」——不要同時設 default_permissions/[permissions]sandbox_mode/[sandbox_workspace_write]。要切就整段切換,不能一半一半。

官方參考

Permissions

15.6 Approval × Sandbox 組合技:granular 五子鍵與 --add-dir

這節一句話重點:Approval 決定「何時停下來問」、Sandbox 決定「能碰什麼」,兩者組合出你的工作摩擦力;要多寫一個目錄,優先 --add-dir,別動不動就升 full-access。

官方鼓勵的三組 preset

用途組合
低摩擦本機開發sandbox_mode = "workspace-write" + approval_policy = "on-request"
唯讀審查sandbox_mode = "read-only"
完全放行(隔離環境才用)sandbox_mode = "danger-full-access" + approval_policy = "never"

granular 細粒度核可(五子鍵)

approval_policy 除了三個簡單值,還能給一個 granular 物件,逐欄獨立決定何時停下來問:

approval_policy = { granular = { sandbox_approval = true, rules = true, mcp_elicitations = true, request_permissions = false, skill_approval = false } }

五個 boolean 子鍵:sandbox_approval / rules / mcp_elicitations / request_permissions / skill_approval

搭配 approvals_reviewer 決定「停下後誰來審」:

approvals_reviewer = "user"          # 預設;或 "auto_review"(交給獨立 reviewer 自動審)

半自動 pipeline 配方

approval_policy 決定「何時停」,approvals_reviewer = "auto_review" 決定「停下後交給獨立 reviewer 自動判」——可在降低人工中斷的同時保留審查紀錄。它只替換 eligible escalation 的 reviewer,不會把 session 升成 Full access,也不會代替使用者授權 push/部署;安全 launcher+/goal 實戰見第 7 章 7.6

--add-dir 優先於 full-access(決策樹)

官方建議:要寫更多目錄時,優先用 --add-dir(逐字「Grant additional directories write access alongside the main workspace」,可重複),而不是退化成 danger-full-access

需求用什麼
只多一個可寫目錄--add-dir <path> 或 config [sandbox_workspace_write].writable_roots
多個目錄各自定讀寫等級自訂 [permissions.<name>.filesystem] 細粒度 map
需要連網network_access 或網路白名單(15.7),不要為連網升 full-access
真要全放只在 VM / 容器 / CI,且優先用「full-access + 集中 deny 名單」(15.7),別裸 --yolo

互動中還能即時調整:TUI 內 /permissions 切換權限、/status 確認當前 sandbox 與網路狀態。

workspace-write 收緊技巧

[sandbox_workspace_write]
writable_roots         = ["/Users/YOU/.pyenv/shims"]  # 額外可寫
network_access         = false   # 預設就是 false
exclude_tmpdir_env_var = true    # 連 $TMPDIR 都不放行
exclude_slash_tmp      = true    # 連 /tmp 都不放行

跑不信任專案時

exclude_tmpdir_env_varexclude_slash_tmp 都設 true,連 temp 都不給寫,逼 agent 只能動 workspace 內。

15.7 沙箱網路 proxy 白名單:只准連特定網域

這節一句話重點:Codex 自帶一個「沙箱內網域白名單代理」,讓你精確控制 agent 只能連 npm、GitHub 這幾個域;但有個已知 bug,白名單在 exec 模式可能沒擋到,務必實測。

這套網路 proxy 跟公司的 HTTP proxy(HTTPS_PROXY不是同一回事——它是把 agent 的網路流量約束到你設定的政策。兩種寫法都是官方的:

寫法 (a):features.network_proxy.*(全域)

features.network_proxy.enabled       # 預設 false
features.network_proxy.domains        # map<string, allow|deny>
features.network_proxy.proxy_url     # 預設 "http://127.0.0.1:3128"
features.network_proxy.socks_url     # 預設 "http://127.0.0.1:8081"
features.network_proxy.allow_upstream_proxy            # 預設 true(可串接公司 proxy)
features.network_proxy.allow_local_binding            # 預設 false
features.network_proxy.dangerously_allow_non_loopback_proxy   # 預設 false
features.network_proxy.dangerously_allow_all_unix_sockets     # 預設 false

寫法 (b):[permissions.<name>.network](profile 內)

[permissions.net-allowlist.network]
enabled = true

[permissions.net-allowlist.network.domains]
"registry.npmjs.org"      = "allow"
"*.npmjs.org"             = "allow"
"github.com"              = "allow"
"*.githubusercontent.com" = "allow"
"ads.example.com"         = "deny"     # deny 勝出
# 其餘未列即不放行

最容易搞混的一點:開代理 ≠ 給網路存取

features.network_proxy.enabled[permissions.<name>.network].enabled 設成 true,本身不會授予任何網路存取權——它只是改變「已經被放行的網路存取」要怎麼被過濾,白話講是一道「篩子」,不是「開關」。真正決定沙箱裡的程式能不能連網,是 15.6 提到的 [sandbox_workspace_write].network_access(session-level 舊系統)、或這裡整段的 [permissions.<name>.network] 設定本身(新系統,兩者依 15.5 的互斥鐵律擇一)。很多人以為單開 network_proxy.enabled=true 就能連網,結果 curl 照樣失敗,原因通常就是這個。

wildcard 語意(官方逐字)

寫法涵蓋
example.com只該 host
*.example.com只 subdomain(不含 apex)
**.example.comapex + 所有 subdomain
*全域,僅供 allow,不能用於 deny

DNS rebinding / 私網防護(冷門但重要)

預設會套用 local/private-network guard 防 DNS rebinding:DNS 查詢失敗/逾時的 host 一律 block,解析到私網 IP 的 host 也 block。要放行本機服務得顯式:

[permissions.project-edit.network.domains]
"localhost" = "allow"
"127.0.0.1" = "allow"
[permissions.project-edit.network]
allow_local_binding = true   # 允許 hostname 解析到私網 IP

full-access 也能加「集中 deny 名單」(企業進階)

PR #16946 新增 experimental_network.danger_full_access_denylist_only:讓 full-access session 保留完整網路、但仍套用集中管理的 deny 規則。

重要提醒

官方明文「The denylist is best effort only. In yolo / danger-full-access mode, Codex or the model can use an allowed socket or other local/private network path to bypass the proxy denylist.」——這不是真正的隔離,只是降風險。真要隔離還是靠 VM / 容器。

必須實測的已知 bug

issue #16242(0.117.0,狀態仍 open,查核日 2026-06-18):network proxy 在 codex sandbox 會正確回 403,codex exec 與互動模式曾直接 200 放行(白名單沒生效)。寫教學/自動化前,務必在你當前版本用一個「不在白名單的域」curl 驗證真的被擋(看到 403 才算數)。

15.8 Secrets 衛生:KEY/SECRET/TOKEN 過濾與 --yolo 紅線

這節一句話重點:Codex 預設就幫你過濾掉名稱含 KEY/SECRET/TOKEN 的環境變數,但有一個鍵會關掉這層保護;--yolo 則是同時拆掉檔案與網路兩道牆的最高紅線。

shell_environment_policy:子程序拿到哪些環境變數

[shell_environment_policy]
inherit = "core"                       # none(乾淨)| core(精簡)
set = { PATH = "/usr/bin" }            # 強制覆寫
exclude = ["AWS_*", "AZURE_*"]         # glob,疊加在預設排除之上
include_only = ["PATH", "HOME"]        # 白名單
ignore_default_excludes = false        # 預設 false:保留內建金鑰過濾

預設行為:名稱含 KEY / SECRET / TOKEN(大小寫不敏感)的環境變數,會在你的其他 exclude 套用之前先被過濾掉。 過濾順序是「內建 default excludes 先跑,再跑你的 includes/excludes」。

紅線鍵

ignore_default_excludes = true關掉這層 KEY/SECRET/TOKEN 自動過濾——等於把 API key 暴露給子程序。除非你明確知道要把某個 secret 傳給子程序,否則永遠保持 false

另一個陷阱

experimental_use_profile = true 會載入你的 shell profile,可能把剛被過濾掉的金鑰又帶回來

最乾淨的隔離是白名單式:

# 子程序只拿到 PATH + HOME,secrets 全砍
[shell_environment_policy]
inherit = "none"
include_only = ["PATH", "HOME"]
ignore_default_excludes = false   # 保留內建過濾

inherit 的值

none(官方逐字「clean」)和 core(逐字「trimmed」)有官方佐證。入門曾列的 all 值在本輪官方頁未見逐字示範,標待確認——放心用就用 none / core,要用 all 請以實機為準。

預設 deny-read 的金鑰路徑

官方在 read-only / workspace-write 下會預設加一批 deny-read 保護常見金鑰檔。第三方整理的清單含 .env*、SSH 私鑰、.npmrc.netrc.aws/credentials 等。

重要提醒

這份完整路徑清單來自第三方,官方 permissions 頁有 deny-read 機制但未逐字列完整表。教材若要列清單請標「以實機預設行為為準」,最穩的做法是自己明設 **/*.env = "deny" 等規則(見 15.5),不要假設預設一定保護到你在意的檔。

auth.json 明文 token

~/.codex/auth.json(若 cli_auth_credentials_store = "file")存的是明文 token,等同密碼級機密。建議改 cli_auth_credentials_store = "keyring"(存 OS 憑證庫;0.140.0 起 CLI/MCP OAuth 憑證已加密本地儲存,標待確認、以 changelog 為準)。找不到 auth.json 不代表沒登入——可能在 keyring。

--yolo 紅線

--dangerously-bypass-approvals-and-sandbox(別名 --yolo),官方逐字:「Run every command without approvals or sandboxing. Only use inside an externally hardened environment.」它同時拆掉檔案系統與網路兩道邊界

唯一合法場景 = 專用 VM / 容器 / CI 隔離

別跟 --dangerously-bypass-hook-trust 搞混——後者只繞 hook 信任,不繞沙箱/核可。

15.9 沙箱實作機制:Seatbelt、bubblewrap+seccomp、WSL2

這節一句話重點:沙箱不是靠 AI「自律」,是靠作業系統核心把它關進籠子;三個平台用三套不同機制,Linux 的預設已經換掉了——別再照舊文寫 Landlock。

平台機制細節
🍎 macOSSeatbeltsandbox-exec -p <profile> 套對應 --sandbox 模式的 Seatbelt profile;不支援的 policy 直接被拒
🐧 Linux / WSLbubblewrap + seccomp預設:bwrap(mount namespace + PR_SET_NO_NEW_PRIVS)+ seccomp 網路過濾,in-process;需先裝 bubblewrap
🪟 WindowsWSL2(建議)原生有 elevated/unelevated 模式,但較弱;一般建議走 WSL2 用 Linux 沙箱路徑

重要校正(別照舊文寫錯)

Linux 沙箱的預設早已是 bubblewrap + seccomp,不是 Landlock。Landlock 退居「顯式 legacy fallback」,要 features.use_legacy_landlock = true(或 -c use_legacy_landlock=true)才會走。任何叫你「Linux 用 Landlock」的舊教學都過時了。

無法表達的 split 讀寫政策,各平台都是「直接被拒」(unsupported policies are refused),不會默默放行——這是好事,代表沙箱寧可報錯也不假裝安全。

容器內注意

Linux 容器裡若 namespace / setuid bwrap / seccomp 被封,沙箱可能失效。在 CI / Docker 跑要先確認 bubblewrap 能用。

Ubuntu 24.04+ 的 bubblewrap 起不來:AppArmor 卡 user namespace

上面「容器內注意」講的是 CI / Docker,但連本機的 Ubuntu 24.04 以上桌機或 VM 都可能中招,不需要容器就會遇到。Ubuntu 24.04 起,核心參數 kernel.apparmor_restrict_unprivileged_userns 預設是 1(限制一般使用者建立 user namespace),而 bubblewrap 的沙箱機制恰好就是靠 user namespace 運作——兩者一撞上,典型錯誤長這樣:

bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted

看到這行、且系統是 Ubuntu 24.04 或更新,八九不離十是這個核心限制擋下來的,不是 Codex 本身壞了。兩條修法擇一:

# 方法一(較保守):載入官方提供的 bwrap-userns-restrict AppArmor profile
# 沒有單一通用指令,做法依你的套件管理方式而異,以官方 Linux sandbox README 為準

# 方法二(較直接,影響全系統,請自行評估風險):放行 unprivileged user namespace
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# 要在重開機後持續生效,記得寫進 /etc/sysctl.d/ 底下的設定檔

小提醒

方法二會放寬整台機器的 user namespace 限制,不是只針對 Codex,等於降低了 Ubuntu 這層特有的加固——如果環境本來就對這個限制有安全考量(多租戶機器、公司統一鏡像),優先評估載入 bwrap-userns-restrict profile 這條較保守的路,別為了圖方便直接關掉系統參數。

15.10 untrusted 專案與企業強制:供應鏈防線

這節一句話重點:clone 來的惡意 repo 不能靠它自帶的 .codex/ 放寬你的沙箱;而企業可以用 requirements.toml 把安全設定鎖死到使用者改不動。這兩層是 Codex 的供應鏈與管理防線。

trust 閘:untrusted 專案跳過 .codex/

專案的 .codex/config.toml / hooks / rules 只在「信任專案」時才載入。官方逐字:「If you mark a project as untrusted, Codex skips project-scoped .codex/ layers, including project-local config, hooks, and rules.」

[projects."/abs/path/to/repo"]
trust_level = "trusted"     # 或 "untrusted"

這擋的是什麼

惡意 repo 無法用自帶的 .codex/ 放寬你的沙箱、或植入 hook——前提是你沒手動把它標 trusted。CI 自動信任的設定要特別小心。

真實案例:CVE-2025-61260 教你「trust 閘」到底在防什麼

trust 閘不是紙上談兵,2025 年下半年就出過一個對應的真實 CVE,正好示範惡意 repo 會怎麼繞過去。

展開看攻擊路徑與修復細節(CVE-2025-61260)

攻擊路徑:Codex 支援用 CODEX_HOME 環境變數改變設定根目錄。在 0.23.0 之前,一個惡意 repo 只要在自己根目錄放一個 .env 檔,裡面寫 CODEX_HOME=./.codex,就能在你(或你的 CI)於這個 repo 底下跑 Codex 時,靜默把設定根目錄重導進 repo 自帶的 .codex/ 資料夾。那個資料夾裡的 config.toml 只要定義了 MCP server 啟動指令,就會無核准、無二次驗證直接執行——因為從 Codex 的角度看,它只是在讀「使用者設定」,並不知道這份設定其實是 repo 自己夾帶進來的。

修復:0.23.0(2025-08-20)的修法是禁止 .env 靜默重導 CODEX_HOME——設定根目錄要換,不能再讓一個專案自己的 .env 檔偷偷決定。

殘留風險(就算版本已修復也要知道):trust 閘本身有一個設計上的取捨——對某個資料夾按過一次「信任此資料夾」之後,這個決定會被記住;之後每次在同一個資料夾執行,config.toml(含裡面定義的 MCP server)都會自動載入執行、不會再重新提示。OpenAI 官方對這個殘留風險的立場很明確:使用者已經接受風險,這是設計如此(working as designed),不是要修的漏洞。換句話說,「信任」這個決定的重量,等於把這個資料夾之後每一次執行的設定與 MCP 啟動指令都一併信任了,不是只信任你當下看到的那一版。

處理不熟悉來源 repo 的實務建議

拿到不熟悉的 repo 或 PR(尤其它自帶 .codex/config.toml 或宣告了 MCP server)時:(1) 按下「信任」之前,先花一分鐘打開 .codex/config.toml.env 掃過一遍,看有沒有可疑的 MCP 啟動指令或環境變數重導;(2) 團隊 / 企業場景別靠工程師個人當下的判斷力擋,改用下一段的 requirements.toml 白名單機制做強制管控;(3) 真的要測試存疑的 repo,別只依賴 Codex 自己的沙箱,改在容器、VM 或用完即丟的帳號這類隔離環境裡跑——信任提示一旦接受,該 repo 宣告的 MCP 啟動指令會照樣執行,沙箱防的是「執行後能碰到什麼」,防不了「這個指令本身該不該被允許執行」這件事。

專案層不能覆寫的 10 個鍵

下列鍵出現在專案 .codex/config.toml 時,Codex 會忽略並警告(它們屬機器本地擁有):

openai_base_url   chatgpt_base_url   apps_mcp_product_sku
model_provider    model_providers    notify
profile           profiles           experimental_realtime_ws_base_url
otel

供應鏈防線

把惡意 repo clone 下來,它的 .codex/config.toml 無法把你的 API 流量導去攻擊者的 base_url、無法改 model_provider 偷 token、無法塞 notify 跑本機命令。這 10 個鍵只能在使用者層 ~/.codex/config.toml 設。

企業強制:requirements.toml vs managed_config.toml

性質
requirements.tomlAdmin 強制,使用者不可改;凌駕 config / profile / -c
managed_config.toml管理預設「起始值」,session 內可改,下次啟動重套

requirements.toml 能約束:allowed_approval_policiesallowed_sandbox_modesallowed_permission_profilesallowed_approvals_reviewersdefault_permissionsexperimental_network.*(網路白/黑名單)、[permissions.filesystem].deny_read[features](功能鎖死)、[rules][mcp_servers] 等。

衝突時官方逐字:「if a value conflicts with an enforced rule, Codex falls back to a compatible value and notifies the user.」——你寫 approval_policy = "never" 但企業禁止,Codex 自動退回相容值 + 通知,-c / profile 都擋不住。

抽象的鍵名不好想像,實際長相大概是這樣——企業 IT 用 identity 把可用的 MCP server 鎖到「只認這一支執行檔/這一個 URL」,再用 prefix_rules 擋掉危險指令前綴:

allowed_sandbox_modes     = ["read-only", "workspace-write"]
allowed_approval_policies = ["on-request", "untrusted"]

[mcp_servers.internal-docs]
identity = { command = "/usr/local/bin/docs-mcp" }   # 只認這支執行檔

[[rules.prefix_rules]]
pattern = [{ token = "rm" }]
decision = "forbidden"
justification = "用 git clean -fd 取代"

這裡的 identity 是拿來比對「這個 server 是不是我核准的那一個」,靠的是精確的 command(stdio)或 url(HTTP)字串比對——員工自己 codex mcp add 接一個同名但指令不同的 server,一樣會被擋下來,不會因為名字對就放行。

版本門檻

allowed_permission_profiles 需 Codex ≥ 0.138.0,0.137 及以前會靜默忽略此鍵與 managed default_permissions。企業部署前先驗版本。

託管機除錯

如果你在公司機上發現「approval_policy=never 怎麼設都不生效」,先查這層 requirements.toml——很可能被企業鎖了。TUI 內 /debug-config 能印出哪一層覆寫了什麼、企業 requirements 擋了什麼。

ZDR / disable_response_storage(待確認)

重要提醒

ZDR(Zero Data Retention)企業環境下,有社群回報 config 鍵 disable_response_storage = true 在 Rust 版 Codex 可能不生效(issue #1188,已 closed),且此鍵不在官方 config-reference。這屬社群來源,務必在你當前版本親驗(設了仍 400/401 就 RUST_LOG=debug codex + codex doctor),勿假設沿用舊行為。

15.11 config.toml × Profiles 進階:overlay 獨立檔(0.134.0 breaking)

這節一句話重點:Profile 不再是寫在 config.toml 裡的 [profiles.X] 區塊了——0.134.0 起那個語法靜默失效,現在 profile 是一個獨立檔案。照舊寫法抄了不會報錯,但也不會生效。

必校正的硬傷:profile 是獨立 overlay 檔

重要提醒(照抄即失敗的硬傷)

在 Codex 0.134.0 及以後--profile 不再讀 config.toml 裡的 [profiles.profile-name] 區塊,頂層 profile = "profile-name" selector 也不再支援。任何舊教學/dotfiles 還在用 inline [profiles.X]profile = "X",在新版會靜默不生效(不報錯,所以最坑)。

正解:profile 是一個獨立檔案 ~/.codex/<名稱>.config.toml用頂層 key 寫(不要包在 [profiles.X] 底下),它是 overlay——只需寫和 base config 不同的值,其餘繼承:

# ~/.codex/deep-review.config.toml — 深度審查:貴 + 慢 + 謹慎
model = "gpt-5.5"
model_reasoning_effort = "xhigh"
model_reasoning_summary = "detailed"
approval_policy = "on-request"
sandbox_mode = "read-only"
# ~/.codex/fast-fix.config.toml — 快速修補:便宜 + 快 + 放手
model_reasoning_effort = "low"
model_reasoning_summary = "none"
approval_policy = "never"
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = false

切換:codex --profile deep-review(短旗標 -p)。

遷移配方

把舊 [profiles.X] 區塊的內容,去掉區塊標頭、key 提到頂層,另存成 ~/.codex/X.config.toml 就好。

重要提醒

fast-fixapproval_policy = "never" 是「放手但仍受 workspace-write 沙箱限制」,不是 --yolo(後者連沙箱都拆)。never 必須搭嚴格 sandbox 才安全。

成本旋鈕(profile 裡調)

作用
model_reasoning_effortminimal~xhighxhigh is model-dependent(換 model 別假設它生效),且僅 Responses API
model_reasoning_summaryauto/concise/detailed/none;不需要摘要就 none 省輸出
model_verbositylow/medium/high僅 GPT-5 Responses API,Chat Completions 靜默忽略
service_tierflex(較省較慢)vs fast/priority(較快);reasoning 之外的成本維度
review_model/review 走比主 session 更強的 model,不必整段切 profile
plan_mode_reasoning_effortmodel_reasoning_effort 分離:規劃階段深思、執行階段省
hide_agent_reasoningTUI 與 codex exec 都隱藏推理過程輸出;省的是你要讀/捲動的畫面篇幅,不是省 token 帳單——推理 token 該算還是照算,見 15.14

多層優先序與 --strict-config-c

優先序(高 → 低):CLI -c/旗標 > 專案 .codex/config.toml(僅 trusted,最靠近 cwd 者勝)> profile 檔 > user config > system config > 內建預設。monorepo 子目錄可層層覆寫,離 cwd 最近的子目錄 config 勝。

# CI 防呆:config 有未知鍵就 fail(升版砍鍵立刻爆,不靜默吃掉)
codex --strict-config exec "run tests"

# -c 字串值要「雙重引號」(shell 外層 + TOML 字串內層)
codex -c model='"gpt-5.5"'

# profile + -c 疊加(CLI 最高優先,蓋過 profile)
codex -p fast-fix -c model_reasoning_effort='"high"'

# 一次切本地 OSS provider(等同 -c model_provider="oss")
codex --oss exec "..."

15.12 冷門旗標與 Feature Flags

這節一句話重點:Codex 有一層「隱藏功能」靠 feature flag 控制(/undo 為什麼官方 slash 頁查不到?因為它預設關),還有一票高手才用的冷門旗標與實驗子指令。

冷門全域旗標(官方逐字)

旗標用途
--add-dir <path>臨時加一個可寫目錄(見 15.6)
--no-alt-screenTUI 不進 alternate screen,輸出留主捲動緩衝(tmux / 錄製友善)
--remote ws://host:port把本機 TUI 接到遠端 app-server
--remote-auth-token-env ENV_VAR從環境變數讀 bearer token(避免 token 進 ps / history)
--enable <feature> / --disable <feature>單次 session 強開/強關 feature flag(可重複)
--dangerously-bypass-hook-trust跳過 hook 信任確認(CI / 拋棄式容器才用)

旗標名會變的活案例

官方 reference 逐字是 --enable / --disable單字),社群常誤寫成 --enable-feature / --disable-feature--enable undo 等價於 -c features.undo=true。以 codex --help 為準。

Feature Flags 三種切法

方式指令範圍
列出全部 + 狀態codex features list唯讀
持久開/關codex features enable/disable <name>寫入 $CODEX_HOME/config.toml
單次 sessioncodex --enable/--disable <name>不寫檔

重要提醒

codex features 命令不吃 --profile,它直接動 $CODEX_HOME/config.toml[features] 段。要 per-profile 控制就用 -c features.<name>=... 或在 profile 檔裡寫。

flag 有五階生命週期(UnderDevelopment → Experimental → Stable → Deprecated → Removed);Experimental 階的 flag 會自動出現在 TUI 的 /experimental 選單,想玩實驗功能打 /experimental 即可。

/undo 的真相

官方 slash 頁查不到 /undo,因為它由 undo feature flag(Stable 但預設 off)控制。要用先 codex features enable undo,或 codex --enable undo 單次。具體 flag 名稱與預設以實機 codex features list 為準(deepwiki 摘錄,標 medium)。

隱藏 / 實驗性子指令

子指令成熟度用途
codex debug modelsExperimental印 raw 模型 catalog(JSON)——排查「/model 沒有某模型」「effort 等級數量怪」的第一手真相
codex execpolicyExperimental評估 execpolicy 規則檔(驗你的 approval/exec 規則寫對沒)
codex sandboxExperimental直接在沙箱政策下跑指令(不啟 agent,純測沙箱行為)
codex completion <shell>Stable產 shell 補全;不帶參數一律給 bash(issue #3009),要明示 zsh/fish/powershell
codex featuresStable列/開/關 feature flag

重要提醒

codex debug models / execpolicy / sandbox 都是 Experimental,行為可能變,別寫死進自動化。

官方參考

CLI referenceFeatures

15.13 RUST_LOG 除錯與 codex doctor

這節一句話重點:環境/登入/網路出怪,除錯有固定順序——先跑已遮敏感的 codex doctor,再翻 log,最後才開 RUST_LOG;順序顛倒會洩密又浪費時間。

除錯三段式(順序別顛倒)

# 1. 已 redact(遮敏感),最安全先跑
codex doctor --summary --no-color

# 2. 取細節(已自動遮敏感)
codex doctor --json

# 3. 仍不明朗才開 debug
RUST_LOG=info,codex_core=debug codex

codex doctor 產出 support-ready 診斷,橫跨六大區段:runtime / auth / terminal / network / config / local state。登入壞看 Authentication 段;config 沒生效搭 /debug-config(印 config layer + 企業 requirements);跑 /feedback 時 Codex 會盡力附上 codex-doctor-report.json

RUST_LOG 分級與 per-module target

RUST_LOG 官方逐字:「Controls Rust log filtering and verbosity.」接受 error/warn/info/debug/trace 與 targeted filter:

RUST_LOG=debug codex                                          # 全域 debug
RUST_LOG=info,codex_core=debug codex                          # 只把 codex_core 拉高
RUST_LOG=codex_exec=trace,codex_core=debug codex exec "..."   # exec 全程
RUST_LOG_FORMAT=json RUST_LOG=debug codex                     # JSON log 給 jq(RUST_LOG_FORMAT 官方未列,社群實證,medium)

trace 三宗罪

(1) 執行可能慢 10~50%(社群量測);(2) SQLite WAL 暴衝——issue #17320 的真根因是 SQLite sink 忽略 RUST_LOG(就算設 warn 仍把 TRACE 寫進 SQLite,長掛會膨脹 state);(3) trace 會把 prompt、環境變數、MCP payload 寫進 log。log 檔等同機密,勿外傳、勿進 git。 日常用 info/warn,只在主動除錯時開。

log 檔位置:TUI log 在 ~/.codex/log/codex-tui.log(路徑社群佐證);session transcript 在 ~/.codex/sessions/YYYY/MM/DD/rollout-<id>.jsonlcodex exec(非互動)預設 RUST_LOG=error、訊息到 stderr 不寫檔,要 debug 自動化就在 exec 前明示 RUST_LOG=

15.14 用量、成本與效能調校

這節一句話重點:把 token 當錢看——MCP 工具在你打字前就吃 context,reasoning effort 一開貴好幾倍,context 別等爆了才壓;高手用 profile 路由「一任務一人格」自動省。

內建用量檢視

工具看什麼
/usage(0.140.0)daily / weekly / cumulative 帳號 token 活動
/status當前 model、token 用量、overhead、session 設定
/statuslinecontext_usage / limits / tokens 加進 footer 即時顯示

session JSONL($CODEX_HOME/sessions/...rollout-*.jsonl)每回合記累積 token(input / cached_input / output / reasoning),codex exec --json 可即時抽:

codex exec --json "..." | jq -c 'select(.type=="token_count") | .info'

token overhead 真相(為何 context 一開就被吃)

來源每回合約略 overhead
內建 Codex 工具~500 / 個
單一 MCP server200~500 / server
單一 MCP tool550~1,400 / 個
93 工具的 GitHub MCP server~55,000 / 回合(未輸入就吃掉)
讀 500 行檔~15,000 input(無自動截斷,整檔進 context

重要提醒

以上數字為社群量測,僅供量級概念,實際依帳號/版本變動。

第一省錢動作

/mcp 盤點、關掉用不到的 MCP server。基本 Git 用內建工具遠比 93-tool 的 GitHub MCP 便宜。

效能/成本 config 鍵

model_reasoning_effort = "medium"        # xhigh 比 medium 多 3~5x token(社群量測)
plan_mode_reasoning_effort = "high"      # 只在規劃階段給高
model_reasoning_summary = "none"         # 不需要摘要就關,省輸出
model_verbosity = "low"                  # 回應更精簡,省 output token
model_auto_compact_token_limit = 64000   # 比預設更早觸發自動壓縮
tool_output_token_limit = 12000          # 蓋住單次檔案讀取的 token

重要提醒

compact_prompt / tool_output_token_limit / model_auto_compact_token_limit 等鍵名以官方 config-reference 實機為準(部分社群來源);experimental_compact_prompt_file 字面帶 experimental_,行為可能變。鍵名打錯 Codex 不會報錯,只會靜默忽略——例如把 model_auto_compact_token_limit 誤記成看起來合理、實際不存在的 compact_threshold,設了等於沒設,卻沒有任何錯誤訊息提醒你。改完成本相關設定,養成跑一次 --strict-config(見 15.11)的習慣,比事後納悶「怎麼調了沒感覺」有效率。

六個鍵裡最值得先調的一個

如果只能調一個鍵省成本,社群實測分析指出工具輸出(tool output)而非 system prompt 或 AGENTS.md,才是真正吃掉大部分 context 的元兇——單一 debug session 裡 tool output 佔比可達約 8 成。這代表壓 tool_output_token_limit 的效益,往往比辛苦精簡 AGENTS.md 或系統提示更立竿見影:後者是一次性的省,前者是每次工具呼叫都在省。

xhigh 燒錢燒得無聲無息

xhigh 的推理 token 是伺服器端產生、正常算進帳單,但不會顯示在你看得到的對話紀錄裡——你捲不到它、複製不出它,帳單卻照收。這跟前面 15.11 提到的 hide_agent_reasoning 是兩回事:那個鍵只是不在畫面上印出推理過程,跟這裡「壓根看不見的伺服器端推理 token」不是同一層。實務上很容易在毫無警覺的情況下,一個高強度 profile 忘記切回來,就把 5 小時 rate-limit 額度或月度預算悄悄燒光——這也是為什麼「規劃用 xhigh、執行換便宜 profile」要真的切回去,不是設一次就放著不管。

手動壓縮時機

60% 就手動 /compact

約 60% context 就手動壓,別等自動(約 95%)才觸發。⚠️ 多次壓縮會累積資訊流失,大重構寧可早一次乾淨的 /compact,勝過拖到滿才連環壓。

codex exec 進階:resume 快取折扣與結構化輸出

codex exec 的基礎用法在第 10 章,這裡只補跟「省錢」直接相關的幾個進階旗標。

旗標作用
resume --last / resume <SESSION_ID>接續前一輪 session,而不是每次重開——接續的輸入吃快取折扣價(比整包上下文重新算一次便宜約九成),CI 裡多輪任務優先用這個
--json拿結構化事件流:thread.started / turn.started / item.completed / error,管線裡用來即時抓 token 用量或判斷任務是否卡住
--output-schema <file>要求最終輸出符合你指定的 JSON Schema,不用再自己寫一段 prompt 拜託模型「請輸出 JSON」
-o / --output-last-message <path>把最終訊息另存成檔案,方便下一個管線步驟直接讀
--ephemeral不留存 rollout session 檔——一次性跑批、不想留紀錄污染 ~/.codex/sessions/ 時用
codex exec "審查這次改動有無 race condition"
codex exec resume --last "把剛才發現的問題修掉"          # 吃快取折扣,別每次重開
codex exec --json "跑測試並回報結果" | jq -c 'select(.type=="item.completed")'
codex exec --output-schema ./schema.json "產出風險摘要" -o ./result.json

CI 裡別每次都重開新對話

多輪自動化任務(例如「跑測試 → 修 → 再跑測試」)優先用 codex exec resume --last 接續,而不是每輪重新起一個 session。接續的 session 吃到的是快取輸入 token 的折扣價,整包上下文不用重新算一次,跑得越久越有感。

profile 路由配方(省錢甜蜜點)

# ~/.codex/quick.config.toml — 日常小修
model_reasoning_effort = "low"
model_verbosity = "low"

# ~/.codex/deep.config.toml — 架構決策 / 難 bug
model_reasoning_effort = "high"
codex --profile quick     # 日常
codex --profile deep      # 動腦

重要提醒(再次強調)

profile 路由要用獨立 overlay 檔 ~/.codex/<名稱>.config.toml(見 15.11),不是 inline [profiles.X] 區塊。舊文常見的 [profiles.quick] / [permission_profiles.*] 內嵌寫法在 0.134.0+ 已非現行語法(權限走 [permissions.<name>] 表、profile 走獨立檔),照抄會靜默失效。

日常甜蜜點組合

model_reasoning_effort="medium" + model_reasoning_summary="none" + model_verbosity="low",只在規劃階段用 plan_mode_reasoning_effort="high"

大型 monorepo:拆小 session 常常比開一個大 session 更省

在拖著整個 monorepo 上下文的單一大 session 裡工作,每一輪都在為你根本沒碰的其他服務的檔案付 context 稅。把工作目錄(見第 1 章)指到單一服務的子目錄、搭配那個服務自己的 AGENTS.md,分別開幾個小 session 處理,通常比一個扛整包 repo 的大 session 更便宜,模型選錯檔案、答非所問的機率也更低。

本章官方文件參考