Hermes cronのPython環境混入を切り分ける


Hermes cronのPython環境混入を切り分ける

定期実行は、一度動いたら安心してしまいがちだ。しかしHermes Agentのcronでは、ジョブ本体だけでなく、起動するシェル、Hermes側の実行環境、対象プロジェクトの仮想環境が重なる。今回は、Python 3.13で動くはずの定期ジョブが、別のPython 3.11用仮想環境にあるNumPyを読みに行き、C拡張の不一致で失敗した事例を切り分けた。

実際に起きたことと確認結果

対象は、Hermes cronに登録したno-agentのスクリプトジョブだった。失敗ログには、対象プロジェクト側のPythonは3.13なのに、NumPyの読み込み元だけがHermes側のPython 3.11用site-packagesになっている、という不整合が出ていた。結果として、`_multiarray_umath`のC拡張が`cpython-313`ではなく`cpython-311`向けとして扱われ、pandasのimport時点で停止した。

日次ノートでは、原因を`PYTHONPATH`または`PYTHONHOME`の混入として整理し、配備先スクリプトで`uv run`の直前に両方をunsetする対処を実施したことが記録されている。Hermesの実行履歴でも、2026-08-06 08:15のジョブは同エラーで失敗し、同日10:52のゲートウェイ経由再実行は完了、翌2026-08-07の定期実行も完了していることを確認した。

仕組みの流れ

cronジョブの流れは次のように整理できる。

1. Hermes cronが、登録済みのスクリプトジョブを起動する。
2. スクリプトが対象プロジェクトへ移動する。
3. `uv run`が対象プロジェクトの仮想環境を確認し、コマンドを実行する。
4. しかし呼び出し元に`PYTHONPATH`などが残っていると、別venvのライブラリがimport候補に混ざる。
5. Python本体とC拡張のビルド対象がずれ、NumPyやpandasのimportで失敗する。
6. `uv run`直前にPython系の外部環境変数を消すことで、対象プロジェクトの環境だけを使わせる。

uvの公式ドキュメントでは、`uv run`はプロジェクト環境を最新にしてコマンドを実行する。一方、Python公式ドキュメントでは、環境変数がインタプリタの挙動に影響し、隔離モードでは`PYTHON*`環境変数を無視できると説明されている。つまり、cronからの起動では「uvを使う」だけでなく、「呼び出し元のPython環境を持ち込まない」ことも確認対象になる。

再現手順

公開用にパスやジョブ名は一般化する。まず、Hermes cronの実行履歴で、失敗時刻とstderrを確認する。

“`bash
hermes cron list
hermes cron runs <job-id>
“`

エラー内で次を見比べる。

  • 実行中Pythonのバージョン
  • importされたNumPyやpandasのパス
  • C拡張ファイル名の`cpython-xxx`
  • 対象プロジェクトの仮想環境パス

対象プロジェクトのPythonが3.13なのに、ライブラリだけ別venvの3.11配下から読まれていれば、環境変数混入を疑う。スクリプト側では、対象プロジェクトへ移動した後、`uv run`の直前に次を置く。

“`bash
unset PYTHONPATH PYTHONHOME
uv run — python -m your_package.daily
“`

変更後は、手動実行だけでなくHermes cron経由でも確認する。対話中のシェルからの実行とGateway経由の実行では、引き継ぐ環境が異なる場合があるからだ。

“`bash
hermes cron run <job-id>
hermes cron runs <job-id>
“`

完了扱いになったら、次回の定期実行でも同じジョブが成功するかを見る。

失敗しやすい点と安全策

一つ目の落とし穴は、NumPyのimportエラーを「NumPyの再インストール問題」と決めつけることだ。ログに別バージョンのPython向けC拡張が出ている場合、パッケージ自体より先に、どのsite-packagesを読んでいるかを確認する。

二つ目は、`hermes cron run`とGateway経由の自動実行を同じものとして扱うことだ。手元の記録では、直接実行時には作業ディレクトリに関する別エラーも出ていた。cronの障害調査では、sourceがdirectかbuiltinか、no-agentかagent実行か、workdirがあるかを分けて見る。

三つ目は、対処を広げすぎることだ。今回の安全策は、プロジェクト実行直前にPython系の外部環境だけを消す小さな変更にした。全環境変数を消すと、APIキー、通知先、ロケールなど必要な設定まで失われる可能性がある。

未完了事項として、Hermes本体の起動補助処理が将来更新された場合、同様の混入が再発しないかは継続監視する。恒久対策としては、ジョブごとの環境を明示し、実行ログにPythonバージョンと主要ライブラリの読み込み元を短く出す方法も有効だ。

再現チェックリスト

  • [ ] `hermes cron runs <job-id>`で失敗時刻とstderrを確認した。
  • [ ] Python本体のバージョンと、importされたライブラリのvenvが一致しているか見た。
  • [ ] C拡張ファイル名の`cpython-xxx`が実行Pythonと合っているか確認した。
  • [ ] `uv run`直前に`PYTHONPATH`と`PYTHONHOME`をunsetした。
  • [ ] 手動実行とHermes cron経由の両方で結果を確認した。
  • [ ] 対処後の次回定期実行が成功したかを追跡した。
  • [ ] ログや記事に実パス、Webhook URL、秘密値を残していない。

公式一次情報

  • [Hermes Agent Scheduled Tasks](https://hermes-agent.nousresearch.com/docs/user-guide/features/cron)
  • [uv: Running commands in projects](https://docs.astral.sh/uv/concepts/projects/run/)
  • [Python: Command line and environment](https://docs.python.org/3/using/cmdline.html#environment-variables)
  • [NumPy troubleshooting import errors](https://numpy.org/devdocs/user/troubleshooting-importerror.html)

Back to top