Python スクリプトが cron で動かない「見えないエラー」

開発環境では何の問題もなく動いていた Python スクリプトが、cron で定期実行しようとすると失敗する──
しかもログにもエラーが残らず、失敗に気づかないという現象は、実務で頻繁に発生します。

line 3: python: command not found

この構成ミスは表面的には "python がない" というだけに見えますが、レビューの立場から見れば、環境構築と運用設計の分離ができていない構造的問題です。

コードの品質が高くても、実行環境の構成に問題があると全体が破綻します。本稿では pyenv + cron による構成トラブルを題材に、レビューアーがどう指摘・補助すべきかを詳述します。

なぜ pyenv 環境で cron は Python を見失うのか

pyenv はユーザー毎にインストールされる Python バージョン管理ツールです。
インストールされた Python は以下のようなユーザー領域に存在します:

~/.pyenv/versions/3.10.4/bin/python

しかし cron は以下のような簡素な実行環境で動作するため、これらのパスを認識できません:

環境変数 cron の実行時値(例)
PATH /usr/bin:/bin
HOME /root または非ログインユーザー
SHELL /bin/sh

したがって、pyenv により設定された pythoncron から見れば「存在しない」コマンドです。

which python で解決しない理由

多くの開発者は次のように考えます:

which python
# /home/user/.pyenv/versions/3.10.4/bin/python

ではこれを cron に直接書けばよいのではないか──と。

しかし、これは根本的に誤りです。なぜなら which が返す値はログインシェルが pyenv のフックを通して構成した PATH を元に解決された値であり、cron ではその PATH が使われないからです。

さらに、pyenv の実態は shims ディレクトリを使ったシンボリックな中継構成であり、直接呼び出すことすらサポート外です。

レビューアーは「which python の出力をそのまま使えば安全」という誤解を構造的に指摘すべきです。

pyenv と cron を共存させるラッパースクリプト

pyenv を cron から使用する場合は、非対話環境で pyenv の初期化処理を明示的に呼び出す必要があります。

#!/bin/bash

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"

# pyenv 初期化
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

pyenv shell 3.10.4

cd /home/user/project || exit 1
python batch.py

この構成でようやく cron から pyenv 環境が再現されます。

レビューアーとして確認すべき点は:

  • pyenv initeval しているか
  • pyenv shell でバージョンを明示しているか
  • PATH に pyenv/bin が明示的に追加されているか
pyenvの初期化不足
@Reviewer: pyenv 環境の初期化が不足しています。`pyenv init` および `pyenv shell` を使ってバージョンを明示してください。cron は非対話環境であるため `.bashrc` が読み込まれません。

root の crontab では動作しない理由

pyenv はあくまでユーザーごとの環境です。よって sudo crontab -e のように root の crontab に登録されたジョブは、たとえスクリプトに pyenv 初期化処理が書かれていても失敗します。

/root/.pyenv/ などは存在せず、pyenv.sh も見つからないため初期化に失敗

cron に登録する際は、必ず 対象ユーザー自身の crontab に登録しなければなりません。

cronユーザーの指摘
@Reviewer: このジョブは pyenv を使用しており、root の crontab に登録されていると失敗します。該当ユーザーの `crontab -e` に移してください。

systemd.timer による代替提案

cron 以外の選択肢として、systemd を利用できる環境であれば systemd.timer による定期実行の方が安定します。

[Service]
Type=oneshot
WorkingDirectory=/home/user/project
ExecStart=/home/user/.pyenv/versions/3.10.4/bin/python batch.py
[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=true

[Install]
WantedBy=timers.target

この方法では:

  • 実行ユーザーを明示可能(User=指定)
  • Environment= により仮想環境の変数も渡せる
  • journalctl によるログ記録が可能
systemdへの移行提案
@Reviewer: 非対話環境の依存が強いため、cron ではなく systemd.timer での実行を検討してください。PATHやユーザー権限を明示でき、ログも管理しやすくなります。

レビュー時のチェックリスト

以下は、pyenv + cron の構成をレビューする際のチェックポイントです。

結論:Pythonコードだけではレビューは不十分

pyenv + cron のような構成では、コードそのものは正常でも実行基盤の不備によって全体が破綻するというケースが多くあります。

レビューアーの仕事は、コードロジックだけを見ることではありません。
そのコードが「いつ、どこで、誰の環境で、どのように実行されるのか」を含めて指摘できてこそ、構造的なレビューが成立します。

静的なコードレビューでは見逃されがちな「実行環境に潜む前提依存」は、
pyenv + cron に限らず nvm、rbenv、Docker、systemd、CI/CD すべてに潜在しています。

レビューとは「コードを見ること」ではなく、「構成と運用を設計通りに動かす責任の共有」であることを忘れてはいけません。