xcodebuild Exit Code 65 只代表建置或測試流程失敗後的終止狀態,不能靠一條通用指令修好;本週應先保留完整命令、原始日誌與 xcresult,找出第一個失敗動作,再依序核對 Scheme、依賴、執行目標、簽署和遠端節點,只有無法穩定重現時才考慮重建。
這篇文章適合三類人:
- 本地 Xcode 建置成功,但遠端 Mac CI 回傳 Exit Code 65 的應用程式開發者。
- 維護共享 macOS 建置節點與 Xcode 工具鏈一致性的 DevOps 工程師。
- 需要劃分編譯、測試、簽署和封存責任邊界的發佈工程師。
先建立故障時間線:退出碼不是根因
里程碑一:保留失敗現場
先記錄實際執行的完整 xcodebuild 命令,包括 -workspace 或 -project、Scheme、Configuration、SDK、Destination、執行帳戶,以及當時使用的 Xcode 路徑。不要只複製 CI 頁面最底部的 Exit Code 65。
測試工作應保留 .xcresult 套件。Apple 的測試結果文件說明,測試結果可用來查看測試會話、目標裝置與相關日誌;這些資料比流水線最後一行更能指出失敗發生在建置、啟動或測試階段。Apple 測試結果與判讀說明
為什麼本地 Xcode 建置成功,CI 卻仍會回傳 Exit Code 65?
因為兩者不一定執行同一個 Scheme、Configuration、Destination 或帳戶。本地圖形介面可能使用已儲存的工作區狀態,而 CI 是另一條明確命令;任何一項差異,都可能讓失敗發生在編譯以前或測試啟動之後。
里程碑二:辨認第一個有效錯誤
從日誌中找出最早出現的具體錯誤,例如找不到套件、某個 Run Script 回傳非零狀態、Destination 不存在,或簽署身分無法使用。後續的「建置失敗」「測試未完成」通常只是連鎖訊息,CI 平台的包裝錯誤也不應取代原始輸出。
Apple 的 xcodebuild 命令列說明可用來核對命令格式與動作範圍;Exit Code 65 本身不會告訴你究竟是哪個專案階段失敗。Apple xcodebuild 命令列技術說明
注意: 不要在尚未定位首個錯誤前刪除 DerivedData、重置 Simulator 或移除憑證。這些動作可能清掉有用證據,也會讓原本可重現的問題變成一次性結果。
專案參數對依賴腳本:先排除命令層差異
第一步:核對 Workspace、Scheme 與 Build Settings
先確認 CI 建置的是 .xcworkspace 還是 .xcodeproj。使用 CocoaPods 或其他工作區整合的專案,若 CI 誤用 project,可能在依賴載入階段便失敗。接著確認 Scheme 是否已共享,並比較本地成功命令與 CI 的 Configuration、SDK、Destination。
可先列出可用 Scheme,再執行一次縮小範圍的命令:
xcodebuild -list -workspace "APP_WORKSPACE.xcworkspace"
xcodebuild \
-workspace "APP_WORKSPACE.xcworkspace" \
-scheme "APP_SCHEME" \
-configuration "BUILD_CONFIGURATION" \
-destination 'platform=iOS Simulator,id=DEVICE_IDENTIFIER' \
-showBuildSettings
不要只看 Xcode 專案介面裡的設定。Apple 說明 Build Settings 會受到不同設定層級影響,而命令列傳入的 Build Settings 優先級較高;因此 CI 腳本中的 KEY=value 可能覆蓋專案原本的值。Apple Build Settings 優先級說明
怎樣判斷是參數錯誤,而不是快取損壞?
把 CI 命令改成與本地成功命令完全一致,並先移除非必要的覆寫參數。若最小命令仍在相同動作失敗,才進入依賴或程式碼層排查;不要把每次參數不一致都歸咎於 DerivedData。
第二步:檢查 Swift Package 與 Run Script
先確認 Package.resolved 是否已提交且內容與本地相同,再檢查私有套件的存取權限。若依賴透過 SSH 取得,CI 執行帳戶需要具備對應私鑰、known_hosts 與代理設定;圖形登入帳戶可正常拉取,不代表無人值守帳戶也可以。
Apple 的持續整合依賴指南特別強調可重現的依賴解析與 CI 環境的工具鏈一致性。依賴解析若是最早失敗動作,後面的編譯錯誤只是結果,不應先改 Swift 程式碼。Apple 持續整合中的 Swift Package 依賴指南
同樣要查看 Run Script Phase 的實際輸出:
- 工作目錄是否與本地不同。
- Shell、
PATH和必要環境變數是否存在。 - 腳本需要的輸入檔是否在前置步驟產生。
- 腳本是否因未處理的命令錯誤而提前結束。
- CI 帳戶是否有權限讀取或寫入指定路徑。
編譯對 Simulator 測試:確認失敗發生在哪個階段
第三步:把 Destination、Runtime 與測試目標放在一起核對
Simulator 啟動失敗確實可能讓 xcodebuild 回傳 65,但這不等於編譯一定有問題。你要先分辨三種狀態:
- 編譯器在產生產品前便失敗。
- 產品已建好,但指定的 Simulator 或 Runtime 不可用。
- 測試目標已啟動,卻在測試會話中失敗。
核對 Scheme 是否支援指定平台,再確認遠端節點實際安裝的 Simulator Runtime、裝置識別碼與架構符合命令。不要用「Simulator 看起來能開啟」當成測試鏈路正常的證據;測試目標、測試 Bundle 和 Destination 必須同時可用。
可先用同一個 Destination 執行最小測試,並保留結果:
xcodebuild test \
-workspace "APP_WORKSPACE.xcworkspace" \
-scheme "APP_SCHEME" \
-destination 'platform=iOS Simulator,id=DEVICE_IDENTIFIER' \
-resultBundlePath "RESULT_PATH.xcresult"
修復後必須使用同一個 Destination 重跑,否則你只能證明「另一台模擬器」成功。從 xcresult 查看測試會話、目標裝置與啟動日誌,才能判斷是 Runtime、啟動服務還是測試本身失敗。
刪除 DerivedData 是否是 Exit Code 65 的標準解法?
不是。只有日誌指向工作區產物或中間檔案不一致,且你已保留原始結果時,才適合清理單一工作區的 DerivedData。清理後若問題消失,仍要用乾淨工作區重跑確認,不能把偶然成功當成根因已證明。
建置對封存簽署:只有證據指向簽署才處理憑證
第四步:分開驗證 build、archive 與 export
若首個錯誤明確包含 Team、Signing Certificate、Provisioning Profile、私鑰或 Keychain,再檢查簽署設定。需要比較的是 CI 執行帳戶能看見的數位身分,而不是你在圖形會話中看到的結果。
核對以下項目:
- Team 設定是否與目標及描述檔一致。
- 憑證的私鑰是否存在,且 CI 帳戶可使用。
- 描述檔是否涵蓋實際 Bundle Identifier 與功能。
- Keychain 是否在無人值守環境中解鎖並可被命令列工具存取。
- 自動簽署是否依賴圖形介面曾經完成的互動流程。
Apple 的命令列工具設定文件可協助確認命令列工具選擇與 Xcode 路徑;簽署問題則應回到建置輸出中的第一個具體錯誤判斷。Apple 命令列工具設定說明
建議將流程拆成兩次驗證:先保存 build 結果,再單獨執行 archive,最後才處理 export。每個階段各自保存日誌與結果。不要以關閉簽署作為發佈任務的通用修復,因為它最多只能協助判斷編譯階段,無法證明可交付的封存流程正常。
遠端節點對專案修復:用復測矩陣決定是否重建
第五步:檢查 Xcode 選擇與節點狀態
在遠端 Mac 上記錄目前的 Xcode 路徑、DEVELOPER_DIR、執行帳戶、磁碟可用狀態、工作區殘留,以及重啟前後的會話差異。相同命令若只在某個節點失敗,才有理由把問題從專案層提升到節點層。
用以下四個結果形成復測矩陣:
- 原始命令失敗,並保存首個錯誤。
- 最小命令在相同工作區的結果。
- 修復後使用原命令重複執行的結果。
- 節點重啟後,在乾淨工作區的結果。
遠端 Mac 出現 Exit Code 65 時,應修復還是重建節點?
若錯誤在不同節點都能以相同命令重現,先修正 Scheme、依賴、測試目標或簽署設定。若只有單一節點失敗,而且 Xcode 路徑、帳戶權限、磁碟狀態或重啟後行為與其他節點不同,先隔離該節點並用乾淨工作區驗證;只有狀態無法穩定重現、修復後仍會回歸,才考慮重建。
這也是遠端 Mac CI 與本地工作站的核心差異:你需要能完整控制執行帳戶、SSH 環境、Xcode 選擇與重啟後狀態。若正在建立節點,可先參考 遠端 Mac 建置環境的驗收方向;需要多個工具鏈並存時,再把 Xcode 路徑切換與隔離納入 Xcode 開發環境方案 的測試規劃。
經驗: 一次成功只代表該次會話成功。至少要完成原命令、相同 Destination、乾淨工作區與重啟後的對照,才足以把節點標記為可回到 CI 服務。
本週執行的決策分支
依照下面條件處理,不要從「刪快取」開始:
- 若首個錯誤是 Scheme、Configuration、SDK 或命令覆寫值不一致,則選擇修正 CI 命令;否則回退到依賴解析檢查。
- 若 Package 下載、私有 SSH 認證或 Run Script 先失敗,則修復執行帳戶與腳本輸入;否則回退到 Destination 與測試會話。
- 若 xcresult 顯示 Runtime、裝置或測試啟動失敗,則修正 Simulator 與測試目標;否則回退到簽署與 Keychain。
- 若只有 archive 或 export 的首個錯誤指向簽署,則分階段處理憑證與描述檔;否則不要關閉簽署。
- 若同一命令在乾淨工作區及重啟後仍只在單一節點失敗,則隔離或重建節點;否則維持節點並修正專案設定。
本地方案通常會把 CI 工作交給一台長期運作的 Windows 或 Linux 主機,再透過虛擬化、轉發或人工操作補上 macOS 工具鏈。這種方式常見的缺點是無法穩定提供原生 Xcode 環境、執行帳戶與圖形會話容易不一致,而且節點重啟後的簽署與 Simulator 狀態難以驗證。若你要重現同一個 Repository 和命令,使用具備完整權限、可以重置並能固定週期保留的真實遠端 Mac,通常比繼續堆疊臨時補丁更容易劃清責任邊界;可先查看 VMSPIN 的遠端 Mac 方案,再依專案的短期復測或持續整合需求評估租用週期。
如果你仍無法把錯誤穩定歸因到專案,先不要更換整套 CI 平台。先用一台可重置的遠端 Mac 重跑相同命令;若錯誤隨節點消失,再進一步評估環境隔離與長期節點安排,這比看到 Exit Code 65 就直接重建更可靠。