先日、noteで「Claude Codeのリモート操作は留守電状態」という記事を公開しました。
公開してすぐ、こんな質問をもらいました。
「PCは確実にスリープしてるのに、なんでClaude Codeが作業できてるんだろう?」
言われてみると、たしかに変。
画面は真っ暗で、見た目はどう見ても寝ています。
それなのに裏では、Claude Codeがファイルを編集し続けていました。
気になったので、Claude Codeと一緒に調べてみました。
pmsetで「誰がスリープを止めてるか」を見る
macOSには、アプリやシステムのプロセスが「今は寝ないでね」と申告する仕組みがあります。
これを電源アサーションと呼びます。
いまどのアサーションが出てるかは、ターミナルでpmset -g assertionsを叩くと確認できます。
下は、この記事を書くにあたって同じ環境であらためて叩いた出力です。
長いので、関係する行だけに絞っています。
Assertion status system-wide:
PreventUserIdleDisplaySleep 0
PreventSystemSleep 0
PreventUserIdleSystemSleep 1
Listed by owning process:
pid 27776(Claude): NoIdleSleepAssertion named: "Electron"
pid 587(ChatGPT): NoIdleSleepAssertion named: "Electron"
pid 429(coreaudiod): PreventUserIdleSystemSleep named: "com.apple.audio.BuiltInSpeakerDevice.context.preventuseridlesleep"
Resources: audio-out BuiltInSpeakerDevice
上半分がシステム全体の集計で、下半分が「どのプロセスが出してるか」の内訳です。
集計のほうではPreventUserIdleSystemSleepが1になっていて、アイドルによるシステムスリープが止められてるとわかります。
逆にPreventUserIdleDisplaySleepは0です。
ディスプレイのスリープは誰も止めていないので、画面が暗くなること自体は、このアサーションでは妨げられません。
「見た目は寝てるのに中身は起きてる」状態の正体は、この組み合わせでした。
犯人その1:AIのデスクトップアプリ
内訳を見ると、まずClaudeのデスクトップアプリがNoIdleSleepAssertionを出しています。
アサーション名は「Electron」でした。
たぶん、Claude Codeの作業中にMacが寝て処理が止まるのを防ぐためだと思います。
おまけで、ChatGPTのデスクトップアプリにも、同じ「Electron」という名前のアサーションが表示されていました。
少なくともこの出力を取ったタイミングでは、AIのアプリ2つがそろって「寝ないで」と申告してたわけです。
ありがたい仕様なんだけど、画面にはどこにも表示されません。
「寝てると思ったら起きてた」の正体は、たぶんここです。
画面を見てるだけじゃ、気づかないのも当然でした。
犯人その2:coreaudiod
もうひとつは少し意外で、coreaudiodという音声まわりのデーモンでした。
名前に「BuiltInSpeakerDevice」とあるとおり、内蔵スピーカーに関するPreventUserIdleSystemSleepを保持していました。
心当たりはありました。
この日は同じMacで、AivisSpeechを使って動画のナレーションを生成していました。
その再生の名残で、音声の出力が開きっぱなしになっていたのではないかと推測しています。
音を鳴らしてる最中に寝られると困るので、音声系がスリープを止めること自体は自然な動きです。
ただ、再生が終わったあとも残っていたのは想定外でした。
2つのアサーションは何が違うのか
名前が違うので、最初は別々の働きをするものだと思っていました。
そこで、macOSの開発環境に入ってる電源管理のヘッダーファイル(IOPMLib.h)を確認してみました。NoIdleSleepAssertionは、Mac OS X 10.7の時点で「非推奨」になった古い名前でした。
代わりに使うよう案内されてるのがPreventUserIdleSystemSleepです。
新しい名前のほうも、役割は「ユーザーが何も操作していないことを理由にしたシステムスリープ」を防ぐこと。
名前は違っても、止めているスリープの種類は同じでした。
仮にClaudeのアプリを閉じても、ChatGPTやcoreaudiodが残ってれば、少なくともアイドルによるスリープは防がれていたはずです。
ただし、蓋を閉じたら話は別
同じヘッダーファイルには、もうひとつ大事なことが書かれていました。
このアサーションが出ていても、蓋を閉じたとき・Appleメニューからスリープを選んだとき・バッテリー残量が少ないときは、システムがスリープすることがある、という一文です。
止められるのは、あくまで「アイドル」が理由のスリープだけなんですね。
これで、以前書いたクラムシェル運用なのにたまにスリープする謎の見え方も変わりました。
あのときの犯人は、pmsetのsleep 120という設定でした。
2時間操作がないとシステムごと寝る、アイドルタイマーです。
つまり当時のスリープは、まさに今回のアサーションが止める「アイドル」が理由のほうでした。
逆に言えば、AIのアプリが「寝ないで」と申告してる間は、同じタイマーが来ても寝ないはずです。
ただし蓋を閉じる操作や電源まわりが理由のスリープは、アサーションが出ていても防げません。
アサーションが出てるから安心、ではなかったんです。
見た目と中身は一致しない
振り返ると、今回の気づきはnoteに書いた記事とちょうど鏡合わせになっていました。
noteのほうは、リモート操作で「動いてる」ように見えて、実際はMacが起きるのを待ってる留守電状態だった、という内容です。
今回は逆で、「寝てる」ように見えて、実際は起きていました。
どちらも、見た目と中身がずれていました。
画面が暗いからといって、スリープしてるとは限りません。
逆に、返事が返ってきたからといって、その場で処理してるとも限りません。
画面が暗いのにMacが起きてる気がしたら、まずpmset -g assertionsを叩いてみるのがおすすめです。
わかるのは「その時点で誰がスリープを止める申告をしてるか」だけで、過去に寝たかどうかまではわかりません。
それでもプロセス名つきで出てくるので、推測で悩むよりずっと早く手がかりがつかめます。
うちのMacの場合は、常駐アプリと音声まわりが並んでいました。

