跳到主要內容
maxstudioNews
AI 自動化DEV Community (Codex)約 11 分鐘

避免 AI 程式碼代理資訊混淆:basou 透過 SessionStart 鉤子實現的三大改進

basou 是一個用於 AI 程式碼代理的工具,它過去會將工作區位置寫入一個所有專案都會讀取的共用檔案,導致不同專案間的資訊洩漏。為了解決這個問題,basou 進行了三項關鍵改進:首先限縮寫入時機,接著在 Codex 支援 SessionStart 鉤子後,完全停止寫入共用檔案。最後,basou 導入了檢查機制,確保只有已註冊的工作區資訊會被傳遞給 AI 代理,從而有效避免了資訊混淆與洩漏。

避免 AI 程式碼代理資訊混淆:basou 透過 SessionStart 鉤子實現的三大改進
圖片來源:DEV Community (Codex)

AI 程式碼代理程式會自動讀取一個檔案,無論您啟動哪個專案。對於 Codex,預設是 ~/.codex/AGENTS.md;對於 Claude Code 則是 ~/.claude/CLAUDE.md。除了儲存在儲存庫中的 AGENTS.md 或 CLAUDE.md 之外,您的家目錄下也有一個,機器上開啟的任何專案中的會話都會讀取它。

這是一個方便的機制,所有專案共享的慣例都集中在一個地方。那麼,如果您將關於特定專案的內容寫入其中會發生什麼事呢?我開發的 basou 有一個名為 basou orient 的指令。它會根據軌跡組裝出您目前的位置以及接下來要做什麼。

如果這個「位置」能在代理程式會話啟動時傳遞給它,您就不必每次都重新解釋。對於 Codex,basou 過去是將其寫入 ~/.codex/AGENTS.md。在「local-first」文章中,我曾寫道,一個簡單的 basou refresh 會每次更新 ~/.codex/AGENTS.md,並稱其「需要小心」且是「預設行為,而非選項」(截至 v0.37.0)。

這篇文章記錄了該預設行為的演變。我限縮了寫入時機,移除了寫入位置,並檢查了傳遞的內容。這花了三個步驟。以下所有內容均基於 basou v0.69.0(2026-10-16)的實作。所涵蓋的變更範圍是 v0.39.0 到 v0.50.0(2026-09-08 至 09-23)。

為什麼會寫入共用檔案?Claude Code 有一個 SessionStart 鉤子。當會話啟動時,它會執行您選擇的指令,並將該指令的標準輸出新增到會話的上下文中。basou 的文件指示您將 basou orient 註冊到這個鉤子上。因此,Claude Code 在會話啟動時就能即時獲得位置資訊。

當時 Codex 沒有等效的功能。v0.30.0(2026-07-01)的更新日誌寫道:「Codex 沒有公開 SessionStart 鉤子,因此這種靜態通道是『我在哪裡』到達互動式 Codex 的唯一供應商中立方式。」

因此,每次 basou refresh 時,basou 都會將位置寫入 ~/.codex/AGENTS.md 中一個由標記分隔的區塊內。它看起來像這樣(僅供說明,非實際輸出):<!-- BASOU:ORIENTATION:START --> # Orientation (the position of the workspace that last ran refresh) <!-- BASOU:ORIENTATION:END -->。

Claude Code 即時獲得資訊,而 Codex 則透過寫入檔案並保留在那裡。這種方式不對稱,因為代理程式的功能不對稱。這並非疏忽,而是受限於當時的條件。所以,這是一個關於當限制解除後,設計如何演變的故事。

什麼資訊洩漏了?一個「位置」包含最新的決策及其原因、尚未解決的問題、已變更檔案的路徑,以及下一步。這正是 basou 從軌跡中組裝出來的。而這些資訊就儲存在 ~/.codex/AGENTS.md 中,屬於上次執行 refresh 的工作區。如果下一個開啟的 Codex 專案不同,Codex 仍然會讀取該檔案。

該區塊會一直保留,直到另一個工作區執行 refresh 並覆寫它。用 v0.39.0 更新日誌的話來說,這是一個「雙向洩漏」:A 的位置會進入 B 的會話,B 的位置也會進入 A 的會話。這並非假設。v0.39.0 的提交記錄顯示,在一個有多個工作區的真實機器上,「三個不同的工作區在數小時內覆寫了同一個區塊」。

數小時內,三個工作區輪流覆寫了同一個區塊,而且過程是靜默的。refresh --json 的結果也沒有提及此事。證據顯示:數小時內,同一個區塊在三個工作區的位置之間切換,在此期間,在另一個專案中開啟的 Codex 會讀取它。我無法判斷是否真的在另一個專案中開啟了會話,或者是否有人讀取了它。但在一個必須確保多個任務不混淆的機器上,這種情況不應該發生。

第一步:限縮寫入時機(v0.39.0)。第一個修復將「是否寫入」變成了每個工作區的宣告。只有其清單宣告 channels.codex: true 的工作區才會寫入。預設是關閉的。宣告 policies.confidential: true 會優先於選擇加入的設定,並阻止其寫入。現在,是否寫入也會顯示在 --json 結果中。這樣一來,未宣告任何內容的工作區就不再寫入。

然而,更新日誌本身也指出了仍然存在的漏洞。這個閘門只控制寫入,它無法阻止另一個工作區已經渲染出的區塊進入當前工作區的 Codex。條件只能限縮寫入端。一個由選擇加入的工作區寫入的區塊,仍然會被每個專案中的 Codex 讀取。讀取端沒有任何改變。

第一步以新增 basou channel clear codex 結束,這是一個手動執行的指令,用於移除已存在的區塊。下一個版本約在 18 小時後發布(09-08,發布時間為 00:29 和 18:39)。

第二步:移除寫入位置(v0.40.0)。此時,Codex 也已經有了 SessionStart 鉤子。basou 的更新日誌指出,它已透過 codex-cli 0.153.4 在 CLI 和桌面應用程式中進行了端到端驗證。在 v0.40.0 中,basou 停止寫入 ~/.codex/AGENTS.md。它也廢除了第一步中新增的選擇加入宣告鍵。

取而代之的是,basou hook install codex 會在 ~/.codex/hooks.json 中註冊一個 SessionStart 鉤子,只執行一次。流程如下:當 Codex 啟動會話時,它會呼叫鉤子(basou hook session-start),並將會話的當前工作目錄(cwd)以 JSON 格式傳遞給標準輸入。

basou 會從當前工作目錄解析工作區,並將與 basou orient 相同的文字列印到標準輸出。Codex 會將其新增到該會話的上下文中。

位置資訊是在那一刻計算出來的,並且沒有儲存到任何地方。一個鉤子服務於所有工作區。然而,沒有任何工作區的位置會被寫入到另一個工作區的會話會讀取的地方。由於共用檔案已從傳遞位置的路徑中移除,這條路徑不再將 A 的位置帶入 B,或將 B 的位置帶入 A。(更新日誌對 Codex 介面而言,稱其為「已關閉:Codex 介面,雙向」)。由於不再需要寫入,第一步的閘門本身也不再需要。

然而,舊版本已寫入的區塊並不會在寫入者停止後消失。單獨升級會將其留在 ~/.codex/AGENTS.md 中。basou hook install codex 和 basou hook status codex 會告訴您是否仍有區塊存在。移除它的指令是 basou channel clear codex,這是第一步中新增的。

細節如何選擇。basou hook install codex 寫入的條目看起來像這樣(在獨立的 HOME 下註冊;路徑已省略):{ "hooks" : { "SessionStart" : [ { "matcher" : "startup|resume|clear" , "hooks" : [ { "type" : "command" , "command" : "node '…/@basou/cli/dist/index.js' hook session-start 2>/dev/null || true" , "timeout" : 30 , "statusMessage" : "basou orient" , "additionalContextLimit" : 0 } ] } ] } }

additionalContextLimit: 0:根據 basou 的更新日誌,Codex 會將超過約 2,500 個 token 的鉤子輸出截斷其頭部和尾部。一個位置資訊會將其開放問題和下一步保留在末尾。如果截斷,您最需要的部分就會消失。

compact 被排除在 matcher 之外:因此,當對話被壓縮時,位置資訊(根據更新日誌估計約 10 KB)不會每次都被重新注入。當它失敗時,正常退出並不列印任何內容:在諸如無法讀取的負載或不是工作區的當前工作目錄等情況下,它會結束而不列印任何內容。註冊的指令本身也被包裹在 2>/dev/null || true 中。不中斷會話是首要考量。

Codex 會跳過鉤子,直到您審查過一次:根據 basou 的更新日誌,新的或已更改的鉤子在使用者信任它之前不會執行。非互動式的 codex exec 會靜默跳過它。basou hook status codex 會檢查 Codex 自己的記錄,並報告鉤子是否受信任。

註冊後,它會顯示:「Codex trust: not yet trusted by Codex (review pending,it is skipped until you trust it)」。

還有一件事:一個人輸入的 basou orient 會重寫 .basou/orientation.md,但鉤子在傳遞位置時不會重寫它。重點是不在您只讀取的儲存庫中留下 orientation.md。

白名單:阻止來自他人儲存庫的資訊。這裡出現了一個新問題。這個鉤子是使用者全域的:Codex 會從任何專案中開啟的會話呼叫它。如果不管,basou 會談論您開啟的任何儲存庫中的 .basou/。對於您自己的儲存庫,這沒問題。問題在於當您複製並開啟一個別人已提交 .basou/ 的儲存庫時。

basou 中的程式碼註解這樣寫道:「將已提交的『下一步』轉化為模型比儲存庫自己的 AGENTS.md 更信任的開發者上下文。」

這篇文章沒有測試模型更信任兩者中的哪一個。但是,別人寫的「下一步」可以在會話啟動時進入上下文,這是您可以自己檢查的。basou init 寫入的預設 .gitignore 會忽略原始日誌(events.jsonl),但不會忽略 manifest.yaml 或每個會話的 session.yaml。

git add -A 會將它們提交。session.yaml 中的標籤會保留記錄的行(如果行很長,則保留其開頭)。在複製的儲存庫中執行一個簡單的 basou orient,作者的「下一步」就會出現。如果不管,這就是會傳遞給會話的內容。

v0.40.0 的解決方案是只談論您自己在 ~/.basou/portfolio.yaml 中註冊的工作區。# ~/.basou/portfolio.yaml version : 1 workspaces : - path : ~/work/alpha。

儲存庫的內容無法重寫此檔案。據我檢查,basou init 也沒有在家目錄端寫入任何內容(正如我在「local-first」文章中寫道,basou 會讀取 ~/.basou/ 下的設定檔,但不會寫入它們)。複製的儲存庫未註冊,因此鉤子會保持靜默。這個註冊表還有另一個用途。那就是第三步。

第三步:檢查傳遞的內容(v0.41.0)。第二步解決了哪個會話獲得哪個工作區的位置資訊。但傳遞的內容尚未被檢查。位置資訊是從記錄的路徑和決策中組裝出來的,因此它可能包含另一個工作區的名稱。例如,alpha 的軌跡可能有一行像這樣(本地重現;$DEMO 是一個臨時目錄):- Next step (recorded, just now): next: check $DEMO/beta/README.md [session ses_01M6DJ6586]。

傳遞給 alpha 會話的位置資訊包含了 beta 的位置。名稱就是透過這樣一行普通的內容進入的。

v0.41.0 開始也使用 portfolio.yaml 中註冊的工作區作為檢查名稱的清單。一個人輸入的指令,例如 basou orient 和 basou refresh,會檢查它們列印的位置資訊,如果發現另一個工作區的名稱,它們會在標準錯誤輸出(stderr)上列印一行提示。

該提示既不引用它找到的名稱,也不引用匹配的字串。它只提供有多少個以及來自哪個檔案的哪幾行。用更新日誌的話來說,這樣警告本身就不會成為它正在報告的洩漏。而且它不會阻擋任何操作。有時名稱是故意存在的,而阻擋一個您每天輸入數次的指令並不是一個選項。

鉤子則不同。它會保持靜默。規範寫道:「鉤子沒有標準錯誤輸出讀取器,其標準輸出成為會話的受信任上下文,因此它會保留位置資訊。」如果鉤子在標準錯誤輸出上列印提示,沒有人會讀取它。而且它的標準輸出會直接成為會話的上下文。因此,鉤子不會傳遞位置資訊。

這與未註冊工作區的情況一樣靜默。您下次輸入 basou refresh 或 basou orient 時就會發現原因。對於相同的檢查結果,它會在人讀取的路徑上發出警告,而在無人讀取的路徑上保持靜默。這看起來不對稱,但背後有一個原因。警告只有在有人

Share

覺得值得一看?分享給朋友。

以上為 AI 自動翻譯導讀。原文版權歸 DEV Community (Codex) 所有, 建議透過「閱讀原文」前往原始網站,以取得最完整資訊並支持原作者。

Newsletter

不用每天追,AI 重點每週寄給你

43 個來源、每 2 小時更新,太多看不完。電子報每週挑最值得看的幾則,附上 Max 的看法。

每週一封。Email 只用來寄電子報,每封信都能一鍵退訂。