目次

macOS LaunchAgent で Thunderbolt NVMe を keepalive する方法(まとめ)

問題の概要

この条件下でThunderbolt NVMe 上に置いた keepalive スクリプトを LaunchAgent で定期実行しようとしたところ、

  1. 手動実行では常に成功(exit 0)
  2. LaunchAgent から実行すると exit 126(EX_NOPERM)
  3. スクリプトは存在し、実行権限もあり、NVMe は unmount されていない
  4. Gatekeeper の quarantine も削除済み
  5. 署名を付けても、削除しても改善しない

という状況が発生した。

原因

LaunchAgent(launchd)は「実行ファイルの実体がどこにあるか」を安全性判定に使う。

つまり:

ここで重要なのは

という点。

解決策の原理

launchd がチェックするのは「実行ファイルの場所」だけであり、スクリプト内部で外付け NVMe を読むことは sandbox の対象外。

よって、

これが launchd の sandbox を完全に回避する唯一の方法。

最終的な解決策(最もシンプル)

スクリプトを使わず、LaunchAgent から 直接 /usr/bin/head を呼ぶ。 /usr/bin/head は内蔵 SSD にあるため、launchd が安全と判断する。

com.keepnvmeawake.plist(最終版)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.keepnvmeawake</string>
 
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/head</string>
        <string>-c</string>
        <string>1</string>
        <string>/Volumes/Home/.keepalive</string>
    </array>
 
    <key>StartInterval</key>
    <integer>60</integer>
 
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

動作確認

launchctl

$ launchctl unload ~/Library/LaunchAgents/com.keepnvmeawake.plist
$ launchctl load ~/Library/LaunchAgents/com.keepnvmeawake.plist
$ launchctl list | grep keepnvmeawake

exit code = 1 になることがあるが、これは launchd が「即終了ジョブ」を失敗扱いにする仕様であり、Thunderbolt NVMe の keepalive には影響しない。

fs_usage

# sudo fs_usage -f filesystem | grep .keepalive

1分ごとに以下のような I/O が出ていれば 完全に成功:

read /Volumes/Home/.keepalive

exit code が 1 になる理由

launchd は「ジョブの最低実行時間」を内部的に持っており、head のような 即終了ジョブは「失敗扱い(exit 1)」になる。

これは 正常挙動であり、keepalive の動作には影響しない。

最終結論