Hermes で Too Many Open Files が出たが、問題はそれだけではなさそうだ
目次
今日は Hermes / OpenClaw 側で起きた不具合をかなり追っていた。見た目は単純そうだったが、実際には一つのまっすぐな原因ではなかった。
最初に目についたキーワードは Too many open files と Errno 24 だった。こういうエラーを見ると、すぐに「fd limit が低すぎるのでは」「ulimit や launchctl、plist を直せばいいのでは」と考えたくなる。
ただ、今日の調査はそこまで単純ではなかった。
最初に壊れたのは機能ではなくツール層だった
最初に目立った失敗は、特定の機能ではなくツール側に出ていた。
いくつかの cron 実行、検索、terminal 呼び出しが、本格的に動く前に次のエラーで落ちていた。
OSError: [Errno 24] Too many open files
この種のエラーが、検索、terminal、定期実行のような無関係に見える場所へ同時に出始めると、調査の視点は「このコマンドが壊れている」では足りなくなる。実行環境全体のリソース・ライフサイクルを疑うべき段階に入る。
そこで疑う対象は一つではない。
- 長寿命プロセスが fd を積み上げていないか
- sandbox や worker の掃除が遅れていないか
- gateway が重複起動していないか
- skill scan、ログ、subprocess pipe が残っていないか
- そもそもの fd limit が低すぎないか
つまり、ulimit -n を上げるだけでは圧力は減るかもしれないが、何が起きたかの説明にはまだ足りない。
現場の数字だけを見ると「今まさに枯渇している」とは言い切れなかった
次にやるべきことは、limit と関連プロセスの fd 数を確認することだった。
まず見えた数字はこうだった。
ulimit -n = 1024
1024 は十分に大きいとは言わないが、見た瞬間に「これでは必ず壊れる」と断定できるほど低いわけでもない。
さらに、その時点で見えた fd 数も極端ではなかった。
- Hermes 関連プロセスが約 97
- gateway 関連の Python プロセスが約 91
その瞬間のスナップショットだけを見るなら、「今この場で fd が完全に枯渇している」とまでは言いにくかった。
ここで最初の引っかかりが出た。ログでは Too many open files が強く出ているのに、現場で測った数字はその結論をまっすぐには支えていなかった。
むしろ説明として自然なのは次の三つだった。
- 以前に fd 圧力があり、ピークはすでに過ぎていた
- 局所的な burst、短命な子プロセス、古い状態が間接的に壊した
Too many open filesは表面症状で、今の起動失敗を直接止めている原因は別に移っていた
調査を進めるほど、今回は三番目に近く見えた。
いちばん怖いログ行が、いまの主因とは限らない
ログを追うと、たしかにかなり強い警告が出ていた。
[Errno 24] Too many open files: '/Users/yougikou/.hermes/config.yaml'
これを見ると、「設定ファイルすら開けないのだから、答えは fd しかない」と言いたくなる。
しかしスタックをさらに読むと、実際に gateway の起動を止めていたのは別の、もっと具体的なエラーだった。
NameError: name 'ensure_open_file_limit' is not defined
これで問題の見え方が変わった。
つまり、たとえ過去のどこかで Too many open files が本当に起きていたとしても、その時点で gateway が起動しなかった直接原因は fd 枯渇ではなかった。直接の原因は、エントリポイントが ensure_open_file_limit() を呼んでいるのに、その import が正しく入っていなかったことだった。
言い換えると、先にリソース圧力があり、そのあとに現場を掴んだ時点では、起動を止めるドミノの先頭が import バグに置き換わっていた可能性が高い。
この点は覚えておきたい。ログの中でいちばん頻繁に出てくるエラーが、いまの故障面に最も近いエラーとは限らない。
plist、launchctl、常駐サービスも無関係ではない
問題がややこしくなった理由の一つは、Hermes の動き方そのものにある。
このマシンでは二つの LaunchAgent が見つかった。
ai.hermes.gatewayai.openclaw.gateway
そして、それぞれに対応するバックグラウンドプロセスも見えていた。
たとえ完全に同じ実行経路ではなくても、少なくとも近い gateway / agent 系サービスが二系統あるということになる。そうなると、リソース消費、重複接続、古いプロセス、混ざったログが、誰が fd 圧力を生んでいるのかを見えにくくする。
さらにログには別のノイズもあった。
- Discord bot token が別プロセスですでに使われていた
- Discord privileged intents の設定も不十分だった
どちらも Too many open files そのものではない。だが、サービスが起動、再試行、失敗、再接続を繰り返す状態を生みやすくする。その結果、失敗した起動サイクルがソケットや fd の寿命を引き延ばしているのではないか、と疑いたくなる。
まだそこまで証明できてはいない。ただ、「limit を上げれば終わり」と考えるよりは、現実のシステムの壊れ方に近い仮説だと思っている。
今日学んだのは、limit の上げ方ではなかった
操作の一覧だけを書けば、やることはおなじみのものばかりだ。
ulimit -nを確認するlaunchctl配下のサービスを見る- LaunchAgent の plist を読む
- gateway の stdout / stderr ログを見る
- プロセスの fd 数を数える
- gateway の重複起動を除外する
ただ、今日いちばん大きかったのは手順表そのものではなく、判断の順番だった。
まず本当にリソース問題なのかを確認する。次に、それが今もリソース問題なのかを確認する。そのうえで初めて、システム設定を変えるべきかを考える。
これは当たり前に見えて、実際には逆にやりやすい。Errno 24 を見た瞬間に「原因は limit が低いことだ」と決めてしまうと、その後の情報を全部その結論の補強として読んでしまう。
でも今日の状況では、ログの文言、現場の fd 数、gateway の現在のクラッシュ地点は、きれいには一致していなかった。
関係はある。だが同じではない。
今の時点での見立て
今日時点では、これは単一のバグというより複合故障に見える。
- 一部は fd 圧力や掃除の遅れに見える
- 一部は常駐サービスの重複や再起動経路の混乱に見える
- さらに一部は fd とは直接関係のない、明確なエントリポイントのバグだった
だから今の Too many open files は、完成した診断名というより症状ラベルに近い。
NameError を直せば改善するかもしれない。fd limit や plist を調整すれば、再現しにくくなるかもしれない。だが、どちらも単独では根因を掴んだ証明にはならない。
まだ結論が出ていない部分:plist をどう変えるべきか
次に本当に検証したいのは、macOS の LaunchAgent plist を変えるべきなのか、変えるならどう変えるべきか、という点だ。
自然な仮説はある。launchd から起動したサービスの fd limit が対話シェルより低い、あるいは実効上の制限が違っているなら、plist に適切な変更を入れることで発生確率を下げられるかもしれない。
ただし、ここにはまだ最終結論がない。
いま手元にある証拠だけでは、plist の変更が正しい方向だと断定するには足りないし、どの変更方法が信頼できるのかを言い切るにも足りない。
なので今日はここで止めておく。Too many open files は確かに主役に見えるが、おそらくそれだけではない。plist をどう書き換えるべきか、そしてその変更で本当に安定するのかについては、まだ調査と検証を続けている段階だ。いまあるのは推測であって、結論ではない。