cron で npm が動かない──それは「設計ミス」かもしれない

静的サイトジェネレーター(SSG)を定期的にビルド・デプロイする構成で、npm run buildcron に登録して自動化するケースは一般的です。

しかし、開発者のローカルではうまく動作していたこの構成が、本番の cron 環境では沈黙する、という事態が頻発します。典型的な出力は以下のようになります。

line 25: npm: command not found

これは単なる「パスの設定ミス」ではありません。レビューアーはこの現象を環境依存の設計破綻として捉え、構成の責務分離や非対話環境の制約を含めて再設計を促す視点が求められます。

which npm でフルパス指定すれば動くのか?

多くの開発者は、cronnpm が動かないと知ると、以下のようにフルパスで指定する回避策をとります。

/usr/local/bin/npm run build

このアプローチは一見合理的ですが、nvm環境では無効です。なぜなら:

  • which npm で得られるパスは、ログインシェル(対話環境)において nvm によって仮想的に解決された結果であり、
  • cron のような非対話環境では nvm.sh が読み込まれておらず、その npm バイナリも解決されないためです。
/home/user/.nvm/versions/node/v22.15.0/bin/npm: No such file or directory

レビューアーは、「which で調べたパスを使えばよい」とする発想そのものが環境構造の理解に欠けているという点を、教育的に指摘する立場になります。

cron はログイン環境を持たない

cron の本質的な特性として、次の点を理解していないコード・構成はレビューで止めるべきです。

特性 影響
対話シェルを起動しない .bashrc, .zshrc などが読み込まれない
PATH は限定的 /usr/bin:/bin 程度
HOME が root の場合がある ~/.nvm が存在しないユーザーで実行される可能性

特に「cron は root で動かせばいい」という発想は nvm の構造と根本的に相容れません。nvmはあくまでユーザーローカルスコープのNodeバージョン管理であり、/root/.nvm/ は存在しない限り失敗します。

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

以下のように、nvm環境の明示的初期化 → Node.js有効化 → ビルド → 配信という一連の流れを明示したラッパースクリプトが必要です。

#!/bin/bash

export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
nvm use 22 >/dev/null

cd /home/user/project || exit 1
npm run build
rsync -av --delete ./dist/ /var/www/html/

レビュー時には、上記のような nvm.sh の読み込み・nvm use・実行ユーザーのスコープが明示されているかを確認してください。

cron に登録すべきは誰か?

0 0 * * * /usr/local/bin/cron-build.sh >> /var/log/cron.log 2>&1

この行が sudo crontab -e など root の crontab に登録されている場合、nvm構成は確実に失敗します。

レビューアーは、cron 登録対象が適切なユーザーかをチェックし、「node/npm がユーザー環境に依存している構成では、必ず該当ユーザー自身の crontab を使う」ことを確認する必要があります。

登録ユーザーに関する指摘
@Reviewer: このcronジョブは `nvm` に依存するため、root の crontab に登録すべきではありません。対象ユーザー(例:deploy用ユーザー)自身の crontab に登録してください。

cron 以外の選択肢:systemd.timer をレビューで提案する

もし対象システムが systemd を使用している環境であれば、systemd.timer によるタスク実行が推奨されます。

[Unit]
Description=Build static site

[Service]
Type=oneshot
ExecStart=/home/user/project/_scripts/build.sh
[Timer]
OnCalendar=*-*-* 00:00:00
Persistent=true

[Install]
WantedBy=timers.target

systemd.timer をレビューで推奨できる理由は次の通りです:

  • 実行ユーザーを明示できる(User=項目)
  • Environment=EnvironmentFile= によるパスの明示が可能
  • journalctl によるログの統合管理が可能
  • タイマー単位での依存管理や再実行制御が可能
cron以外の提案
@Reviewer: この構成は非対話環境での変数依存が強いため、systemd.timer への移行を検討してください。特に `nvm` のようなユーザーローカル管理構成では systemd の環境定義のほうが適しています。

レビュー観点まとめ:非対話環境とスコープの理解を問う

レビューでこの構成に遭遇した際、以下の観点をチェックリストとして使うと構造的な確認が可能です。

結語:コード外にある構成の設計責任をレビューに含める

cron + nvm + npm の構成は、コードそのものに問題がなくても非対話環境・スコープ依存の理解不足によって失敗する事例の代表格です。

レビューアーとして重要なのは、構成そのものがコードと同様に品質を左右する設計要素であるという認識を持ち、非コード領域の設計責任もレビューの射程に入れることです。

静的サイトジェネレータやビルドツールがどれだけ洗練されていても、その実行基盤が適切に設計・文書化されていなければ、安定した運用は望めません。

レビューの対象はファイル単体ではなく、実行環境を含む構成全体です。