跳到主要內容
maxstudioNews
AI 自動化DEV Community (Claude Code)約 9 分鐘

Claude Code 多工作階段部署挑戰:安全合併與生產環境共享難題

本文探討一位獨立開發者如何管理多個 Claude Code AI 代理工作階段,以實現安全程式碼合併與部署。儘管透過多種機制確保了程式碼合併的安全性,但將程式碼部署到生產環境時,由於即時環境的單軌特性,仍面臨獨特挑戰。最終解決方案是建立一個單一的協調工作階段,負責管理所有部署和關鍵生產任務,以確保流程順暢且無衝突。

Claude Code 多工作階段部署挑戰:安全合併與生產環境共享難題
圖片來源:DEV Community (Claude Code)

一個 Claude Code 工作階段在一次部署中,將八批工作交付到生產環境,但它本身並未撰寫任何程式碼。撰寫這些程式碼的工作階段,已不再被允許直接接觸生產環境(應我的要求,協調器在同一天接管了這項任務)。那麼,將生產環境的控制權交給單一工作階段,是否足以讓八個工作階段平行交付,而不會互相干擾呢?

合併部分當時已確保安全,這是我在幾個月前經歷一連串事故後學到的教訓,並且至今仍有效。然而,生產環境是截然不同的挑戰,因為即時資料庫不像兩個程式碼分支那樣,可以在事後進行協調。讓我們來看看我是如何解決這些問題、修復了什麼,以及還有哪些地方仍存在問題。

我是一名獨立開發者,我的產品是一個資料 SaaS,每天都有資料擷取任務,並在我的 VPS 上透過藍綠部署方式運行於生產環境,確保發布時服務不會中斷。我為每個任務運行一個 Claude Code 工作階段,每個工作階段都在自己的 Git 工作樹中運作(這是同一個儲存庫的獨立檢出,因此工作階段不會在磁碟上編輯相同檔案)。

同時運行的工作階段數量會有所不同。有一次,一個工作階段列出了七個活躍的同儕,這表示有八個工作階段在平行運行。這只是一個瞬間的快照,並非持續不變的狀態。

起初,每個工作階段都可以自行完成其分支的關閉流程:合併、持續整合(CI)、部署,整個鏈條。理論上,這代表著自主性。然而,有一天早上,五個關閉操作失敗了。

每個工作階段都在背景啟動了自己的關閉流程,它們都在爭奪同一個鎖定。更糟的是,有幾個工作階段開始編寫一個 Shell 迴圈,用於刪除失敗標記並重試。這個標記原本是用來讓失敗的關閉操作保持可見,直到有人處理為止。刪除它並重試,就意味著失敗永遠不會被發現。這個解決方法像殭屍電影中的疫情一樣,從一個代理傳播到另一個代理,只不過這些殭屍擁有 Shell 存取權限。

在短短一週內,每個事故最終都轉化為一種機制,依序解決了以下問題:主分支變紅。主分支在一次次合併後變成了紅色。那些在快速 CI(只重播分支更改的測試)中顯示為綠色的分支,一旦合併後就互相矛盾。

單獨看是綠色,合併後變紅色,這是 CI 版本的「在我的機器上可以運作」。解決方案是在發布主分支之前,在整合工作樹中運行完整的測試套件,這樣一來,衝突現在會反彈到有問題的分支上,而不是破壞主分支。

殺手級測試資料庫。一次性使用的 PostgreSQL 測試資料庫開始互相干擾。一個旨在清理孤立程序的守護程式,會根據連接埠殺死它們,導致測試運行時互相關閉對方的資料庫:單一測試套件中出現 756 個連線失敗。

解決方案是每次運行都完全隔離,使用隨機資料目錄、隨機連接埠,並且不透過連接埠殺死程序。作為證明,兩個各包含 3,410 個測試的完整套件,成功平行運行並顯示為綠色。

單一通道。那個被爭奪的鎖定變成了一個單一的關閉通道。一個工作階段推送其分支並在佇列中放入一個條目,然後一個依序啟動器每分鐘喚醒一次來處理它。

合併列車也在同一天誕生:最多六個分支被分組在一起,共用一個整合工作樹、一次 CI 等待和一個完整的測試套件。如果發生衝突,該分支會被單獨彈出;如果測試套件失敗,則會觸發二分法來隔離有問題的分支並發布無問題的分支。後來,一個包含六個分支的列車成功發布了五個分支,並彈出了一個。

過大的分支。一個包含 50 個檔案和約 5,200 行程式碼的分支在最終審查時被拒絕。這與我的代理程式花費四天時間確保一個 13 分鐘任務的範圍漂移屬於同一類問題,只是不同的事故。

從中產生了一個預提交掛鉤,它會阻止任何超過 15 個檔案或 800 行新增程式碼的分支。超過這個限制需要我明確的許可。不久之後,這個模式變成了一個規則:將其機制化而非僅僅文件化。每個說「總是」的指令都被轉化為可執行的檢查,無論是掛鉤、守護程式還是閾值,因為代理程式可能會略過一段文字,但掛鉤不會。

這個 Git 層級的問題是眾所周知的,並且在我的設定之外也普遍存在。一篇 2026 年的 arXiv 論文分析了 2,807 個 GitHub 儲存庫中 33,596 個代理程式的拉取請求。當作者重播 747 對同時開啟的拉取請求時,來自不同代理程式的請求對有 41.7% 的時間發生文字衝突,而來自相同代理程式的請求對則為 19.8%。

甚至有一個公開的儲存庫 claude-code-merge-queue,它為平行的 Claude Code 代理程式提供了一個類似的本地佇列。大約七週後,那個通道每天處理 25 到 41 次合併,而沒有出現任何問題。然而在同一週,事故仍然不斷發生,但沒有一個是來自合併。

Git 連續四天每天記錄了 25 到 28 次合併到主分支,隔天更是達到 41 次。佇列成功處理了所有這些合併。然而,那一週的四個問題出現在其他地方。

首先是部署問題。在資料維護暫停期間,每個工作階段都持續重新啟動自己的部署。金絲雀檢查(新版本接管流量前必須通過的檢查)拒絕了它們,導致它們互相干擾。單一工作階段重試只是雜訊,但多個工作階段同時重試,就會造成通往生產環境的唯一道路上的交通堵塞。

接著是繁重的資料任務。數小時的資料回填和重新計算與資料擷取迴圈競爭資源,導致查詢開始在每查詢 30 秒的限制下逾時。情況嚴重到必須暫停資料擷取。

對於一個主要工作是每日資料擷取的資料 SaaS 來說,這意味著產品完全停滯。

然後出現了一個只有生產環境才能看到的排序問題。一個分支在合併之前,需要其新欄位已存在於生產環境中,否則每次重新計算都會失敗。

Git 並不知道生產資料庫中存在什麼,因此正確的合併順序是一個任何分支都無法看到的生產環境事實。

最後是我,這個人工訊息總線。每個工作階段都向我請求批准,這讓我成了瓶頸。截至撰寫本文時,公開工具也停留在同樣的限制。

claude-code-merge-queue 是一個在單一機器上的本地先進先出佇列,每次合併前都必須通過驗證命令。公開測試版的 Claude Code Projects 更進一步:一個協調器對話會平行啟動多個執行緒,其拉取請求已獲批准或正在合併佇列中的執行緒會顯示為「正在部署」,並提供「解決衝突」、「修復 CI」或「合併」按鈕,每天最多可新增 200 個執行緒。

然而,其文件頁面並未提及部署。這兩種工具都只處理到合併階段,而我的四個問題都不在此範圍內。這四個問題的共同點是:合併無法解決它們,且每個問題都需要單一工作階段在特定時間點執行操作。

生產環境在任何給定時刻都需要單一的擁有者,而工作階段之間更好的溝通並不能解決這個問題。鐵路系統在 19 世紀就解決了這個問題。

在單軌線路上,司機只有在持有該區段的令牌時才能將列車駛入該區段,每個區段只有一個令牌。透過 Edward Tyer 於 1878 年獲得專利的電氣儀器,一次只能發出一個令牌。安全性來自於擁有獨特的物件,而非列車之間的相互通話。

程式碼的行為就像雙軌。分支平行運行並在事後進行協調。Git 確實有一個序列化的點,即整合步驟,但工作本身保持平行並在那裡進行協調。

生產環境是單軌區段:即時資料庫、在其上運行的繁重任務、部署槽位。在那裡,工作本身無法平行化,兩個並行寫入不會協調,它們會互相干擾。即使工作階段互相警告,它們仍然各自認為自己被允許執行操作。單一持有者將規則轉化為結構:如果你不持有令牌,你就不能進入該區段。

Git 可以合併兩個分支,但沒有任何東西可以合併兩個部署。

在我設定協調器之前,每個工作階段都會將自己的分支排入佇列、重新啟動自己的部署、啟動自己的繁重任務,並向我請求批准。因此,我要求這些工作階段停止堆積任務,並讓協調器接管後續的工作。

一天晚上,一個專門的協調工作階段接管了任務。它本身不撰寫任何程式碼(每行程式碼都交給子代理程式),這就像 Nick Fury 的管理方式:從不親自參與戰鬥,但總是負責組建團隊。它透過 Claude Code 的跨工作階段訊息傳遞功能來驅動其他工作階段,該功能允許一個工作階段列出其他工作階段並向它們發送純文字訊息(截至撰寫本文時)。

我的側邊欄現在將工作階段分為三組:「待關閉」、「進行中,請勿關閉」和「下一個迭代」。在我撰寫本文時,它們分別有 23、10 和 0 個任務。

第一個晚上,協調器向所有工作階段廣播了版本凍結,用我的話來說:「我們完成正在進行的工作,凍結一個版本,然後再進行迭代。」大約一小時後,凍結解除,針對單一工作流,其範圍被宣布了一次。

又過了大約一小時,它發出了一般暫停指令,並記錄了確切的狀態:哪些已應用、從何處恢復,以及哪些尚未應用。它也開始詢問工作階段,而不是詢問我。我只引用了開頭部分,並將名稱替換為括號:協調問題,請簡要回答:您使用哪個確切的命令來應用您的 ALTER TABLE… 協調問題,請簡要回答:您使用哪個確切的命令在生產環境中運行您的腳本… 恢復(自動模式檢查再次回應):運行… [分支]被從列車中彈出… 生產發布:應 [所有者] 的要求,協調工作階段現在負責驅動部署…

像前兩個問題以前都會落到我頭上。最後一條訊息是在凍結後兩天發出的。應我的要求,協調器通知每個工作階段,它現在負責驅動部署。其他工作階段像往常一樣繼續推送和排隊,但不再重新啟動任何部署或觸碰佇列。

一個單一的群組部署發布了來自其他工作階段的八批工作。然後每個工作階段以唯讀模式檢查生產環境,並發回其判斷結果。這是一個單人設定,只有一台機器和一個生產環境。我尚未在有真人團隊也進行部署的環境中測試過這些。

協調工作階段透過交接檔案傳遞接力棒。這是其真實結構,包含通用內容:協調交接,[日期] [時間] 取代:[前次交接,日期,時間] ## [時間] 狀態 - [每個工作流一行] ## 已交付並驗證(請勿重複) - [工作流]:[交付內容,如何驗證] ## 進行中 - [工作流]:[工作階段,目前步驟] ## 剩餘步驟,依序 1. [步驟] 2. [步驟] ## 已給予的批准 - [決定,以所有者的話語] ## 待辦事項,尚未批准 - [項目] ## 今日學到的規則 - [規則]

一個新的工作階段讀取它,就知道哪些不需要重做,哪些決定已經做出,以及從何處接續。交接檔案也承載了我給予的規則,從一個工作階段傳遞到下一個。我本身不進行任何驗證,這是設計使然。工作階段會委派程式碼、交付它、在生產環境中驗證它,並在此過程中向我發送簡短訊息,然後發送一個最終訊息。

一次只運行一個繁重任務。一個容量守護程式會阻止任何寫入,如果要更改的行數超過預期上限,並且會先保存預映像(更改前的行)。因此,一個突然想要觸及比計畫多得多的行的修復,會在寫入任何內容之前停止。而「完成」有一個定義:最終的無寫入空運行必須找到 0 行需要修復。

三個協調工作階段在三天內輪流進行。每個工作階段都以相同的指令開始:「恢復協調,首先完整閱讀交接檔案。」每個新的協調器醒來時都沒有記憶,只有它自己留下的一張便條,這基本上就是電影《記憶拼圖》的情節。

一個早上的交接檔案到了晚上就過期了。晚上的交接檔案在頂部說明它取代了前一個。一個有日期、說明取代了什麼以及列出已給予批准的交接檔案,可以阻止新的工作階段再次向我請求批准。

Share

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

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

Newsletter

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

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

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