macOS launchd EX_CONFIG 78 — Desktop 資料夾 TCC 限制

升級到 macOS 26.4 後開始出現的問題。macOS 加強了 TCC 對 Desktop/Documents 資料夾的存取限制,launchd 在啟動 job 前會先開啟 stdout/stderr 檔案,若路徑在受保護的目錄下,整個 job 就無法啟動,回報 EX_CONFIG(78)——而且因為 process 根本沒跑到,log 也是空的,很難追蹤。


症狀

  • launchd 顯示 last exit code = 78: EX_CONFIG
  • state = spawn scheduled(持續節流重試)
  • runs = N(已嘗試多次,代表 launchd 有在嘗試,但每次都很快失敗)
  • stdout / stderr log 檔完全空白
  • 手動用相同環境變數執行卻完全正常

根本原因

macOS TCC(Transparency Consent Control)從 Sequoia 起嚴格限制 daemon/agent process 存取以下路徑:

  • ~/Desktop/
  • ~/Documents/
  • ~/Downloads/

launchd 在啟動程序之前就要開啟 StandardOutPath / StandardErrorPath 指定的檔案。若這兩個路徑在受保護的目錄下,launchd 無法取得存取權限,整個 job 啟動失敗,回報 EX_CONFIG (78)

診斷過程

⚠️ 最難追的地方:把 ProgramArguments 換成 /bin/echo test 也一樣退出 78,代表問題不在程式碼,而是在 launchd 的啟動配置本身。

1
2
3
4
5
手動執行正常
└──→ 模擬 launchd 環境(env -i)執行 → 也正常
└──→ 換成 /bin/echo → 仍然退出 78
└──→ 改 log 路徑到 /tmp → echo 成功,退出 0
└──→ 確認是 StandardOutPath 的問題

診斷指令:

1
2
3
4
5
6
7
# 看 launchd job 詳細狀態
launchctl print gui/$(id -u)/com.yourjob

# 關鍵欄位:
# last exit code = 78: EX_CONFIG ← 這就是 TCC 被擋
# runs = N ← 已嘗試 N 次
# state = spawn scheduled ← 節流中

解法

把 plist 的 log 路徑從 Desktop 移到 ~/Library/Logs/

1
2
3
4
5
6
7
<!-- 修改前(失敗)-->
<key>StandardOutPath</key>
<string>/Users/yourname/Desktop/Project/app/logs/app.log</string>

<!-- 修改後(正常)-->
<key>StandardOutPath</key>
<string>/Users/yourname/Library/Logs/app/app.log</string>

記得先建立目錄,然後 reload:

1
2
3
mkdir -p ~/Library/Logs/yourapp
launchctl unload ~/Library/LaunchAgents/com.yourjob.plist
launchctl load ~/Library/LaunchAgents/com.yourjob.plist

⚠️ 踩坑補充

  • WorkingDirectory 指向 Desktop 目前還沒問題,TCC 只擋「開啟檔案」操作
  • 同樣的問題適用於其他 LaunchAgent/LaunchDaemon 的 log 路徑
  • 安全的 log 路徑:~/Library/Logs//tmp//var/log/
  • 這個問題在升級到 macOS 26.4 後才出現,之前設定好好的突然掛掉