「這段 e2e 測試在本機(macOS)跑起來完全正常,為什麼 CI 上的 macOS runner 會炸?」
昨天談到多平台測試矩陣設計的挑戰——今天用一個真實案例,具體走一次「AI 沒考慮到某個平台特有行為,導致回歸」這件事實際發生時長什麼樣。這個案例特別有意思的地方在於:出問題的不是專案自己的邏輯,而是作業系統層級一個大部分人根本不會意識到的限制。
PHPUnit & Pest Test Explorer 這個專案的 e2e 測試(macos-latest job)開始出現失敗,錯誤訊息是:
listen EINVAL: invalid argument .../.vscode-test/user-data/1.12-main.sock
這個失敗最詭異的地方是:它會發生在完全不相關的 PR 上——不是因為那些 PR 改了什麼會影響 e2e 環境的程式碼,而是因為 CI 的 checkout 路徑長度剛好在某個臨界點附近波動。這正是這類問題最難抓的原因:症狀出現的時機,跟造成問題的真正原因,看起來完全不相關。
這個案例後來記錄在 PR #423 裡。追查下去,真實原因是:macOS 對 Unix domain socket 的路徑長度有 104 bytes 的硬性上限(這是作業系統核心層級的限制,不是 VS Code 或這個專案能繞過的規則)。VS Code 測試框架預設的 --user-data-dir 路徑,會巢狀掛在整個 checkout 路徑底下——在 GitHub Actions 的 macOS runner 上,這條路徑(例如 .../vscode-phpunit/vscode-phpunit/packages/extension/.vscode-test/user-data)加上 VS Code 自己要建立的 IPC socket 檔名,長度就超過了這個 104 bytes 的上限,導致 VS Code 啟動時直接崩潰。
更麻煩的是,連「換一個比較短的暫存目錄」這個直覺解法也不完全成立:os.tmpdir() 在 macOS 上回傳的路徑本身也不算短(是一個帶隨機字元、per-process 的 /var/folders/.../T/ 格式路徑),扣掉這條路徑本身的長度,留給 socket 檔名的空間依然不夠。真正的修法是針對 macOS 特別指定一個固定、極短的路徑(例如直接用 /tmp 而不是 os.tmpdir()),其他平台則維持原本的邏輯不受影響。
**如果請 AI 修一個「e2e 測試在某個 CI job 上失敗」的問題,它的第一直覺通常是往程式邏輯或測試腳本本身找原因——因為那是它能直接讀到、能推理的東西。**作業系統核心層級的路徑長度限制,不會出現在任何一行看得到的程式碼裡,只有在查證「這個平台的作業系統對這件事有沒有額外限制」時才會浮現。
用一組對照來看這個差異:
❌ 只在程式邏輯裡找原因:
「e2e 測試在 macOS 失敗,但 Linux 跟 Windows 都正常,
應該是這個 CI job 的環境變數設定有問題,
檢查看看 workflow yaml 裡 macOS 那段設定。」
→ 停留在專案自己的設定檔範圍內找答案,
忽略了「問題可能出在作業系統本身的限制」這個維度
✅ 把「這個平台有沒有特殊限制」納入查證範圍:
「e2e 測試只在 macOS 失敗,錯誤訊息是 socket 相關的 EINVAL,
先查一下 macOS 對 Unix domain socket 路徑長度
是不是有作業系統層級的限制,
再回頭看目前的路徑組裝邏輯會不會踩到這個限制。」
→ 把「症狀只在特定平台出現」當成一個明確的查證方向,
而不是只在專案自己的程式碼裡打轉
「只在某個平台出現」這個線索本身,就是在告訴你答案很可能不在你的程式碼裡,而在那個平台的底層限制裡。 這正是這個系列從 Day 01 開始反覆強調的模式:AI 對「已經查過的範圍」給出的結論再紮實,都無法涵蓋它根本沒想到要查的維度——而「這個平台有沒有特殊限制」正是最容易被漏掉的維度之一,因為它不會顯示在任何一份專案自己的原始碼裡。
這個問題修好之後,留下一個值得記住的紀律:任何跟檔案路徑、暫存目錄、IPC 機制相關的邏輯,只要牽涉多平台,就該把「這個平台有沒有底層限制」列成一個明確要查證的項目,而不是等它在 CI 上炸開才回頭查。 這條紀律成本不高——多問一句「這個平台會不會不一樣」,但省下的除錯時間可能是數倍。
回想你維護的專案裡有沒有類似的跨平台邏輯(路徑處理、檔案系統操作、行程間通訊):你有沒有主動查證過每個目標平台各自的底層限制,還是等出問題了才被迫去查?
明天要換一個角度:外部貢獻者提交的 PR 品質參差不齊,AI 該怎麼在「幫忙補足資訊落差」跟「越界評判貢獻者能力」之間拿捏分寸。