tweet:2026:0706_20260706_1
目次
macOS LaunchAgent で Thunderbolt NVMe を keepalive する方法(まとめ)
問題の概要
sudo pmset -a disksleep 0を実行/Volumes/Home/.keepaliveを作成dd if=/dev/zero of=/Volumes/Home/.keepalive count=1 bs=4096- bsはDiskによる。HDDで古いデバイスなら512でも良い。
- /Volumes/Homeは外付けThnuderbolt SSD
- ~/bin/keepnvmeawake.sh は以下の通り
#!/bin/bash #dd if=/Volumes/Home/.keepalive of=/dev/null bs=4k count=1 2>/dev/null /usr/bin/head -c 1 /Volumes/Home/.keepalive >/dev/null 2>&1 || true exit 0
この条件下でThunderbolt NVMe 上に置いた keepalive スクリプトを LaunchAgent で定期実行しようとしたところ、
- 手動実行では常に成功(exit 0)
- LaunchAgent から実行すると exit 126(EX_NOPERM)
- スクリプトは存在し、実行権限もあり、NVMe は unmount されていない
- Gatekeeper の quarantine も削除済み
- 署名を付けても、削除しても改善しない
という状況が発生した。
原因
LaunchAgent(launchd)は「実行ファイルの実体がどこにあるか」を安全性判定に使う。
- 内蔵 SSD → 安全な実行元
- 外付け NVMe / USB / Thunderbolt → 安全ではない実行元
- symlink → リンク先の実体の場所で判定する
つまり:
- ~/bin/keepnvmeawake.sh は ~/bin が symlink であるため、Sandboxに弾かれる
- script 実体は Thunderbolt NVMe 上
- launchd の sandbox が 「外付けディスク上の実行ファイル」 と判定
- bash がスクリプトを開く段階で exit 126 を返す
ここで重要なのは
- NVMe のデータファイル (.keepalive) は常に読める
- NVMe 上の「実行ファイル」だけが launchd に拒否される
という点。
解決策の原理
launchd がチェックするのは「実行ファイルの場所」だけであり、スクリプト内部で外付け NVMe を読むことは sandbox の対象外。
よって、
- 実行ファイルだけ内蔵 SSD に置く
- NVMe にアクセスするのは「スクリプト内部の処理」だけにする
これが 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 の動作には影響しない。
最終結論
- launchd は外付け NVMe 上の「実行ファイル」を拒否する
- NVMe 上のデータファイルを読むことは拒否しない
- よって、実行ファイルだけ内蔵 SSD に置く必要がある
- 最もシンプルな解決策は plist から直接 /usr/bin/head を呼ぶこと
- fs_usage に I/O が出ていれば Thunderbolt NVMe keepalive は完全に成功
tweet/2026/0706_20260706_1.txt · 最終更新: by seirios
