開発メモ
npm run build を grep や tail に通すと、失敗しても終了コードが0になる(PIPESTATUS / pipefail)
2026-08-06
ビルドの出力が長いので、こう書くことがあります。
npm run build | tail -5
npm run build 2>&1 | grep -E "Compiled|Error"
これをスクリプトやCIで使うと、ビルドが失敗しても成功扱いになります。
何が起きているか
パイプでつないだとき、$? に入るのは パイプの最後のコマンドの終了コードです。tail や grep は自分の仕事に成功しているので、0 を返します。前段が落ちていても関係ありません。
実際に確かめます。
false | tail -1
echo $?
# → 0
false は必ず失敗するコマンドですが、$? は 0 です。
各パイプの終了コードを見る
前段が本当はどうだったかは、配列で取れます。シェルによって変数名が違います。
bash
false | tail -1
echo "${PIPESTATUS[@]}"
# → 1 0
zsh(macOSの既定シェル)
false | tail -1
echo "${pipestatus[@]}"
# → 1 0
zshは小文字の pipestatus です。bashの癖で $PIPESTATUS と書くと、zshでは空になります。ここが地味に引っかかります。
左が前段(false)の 1、右が tail の 0。前段は失敗していたと分かります。
直し方
方法1: pipefail を有効にする(推奨)
パイプの途中で失敗したら、パイプ全体を失敗にします。
# bash
set -o pipefail
npm run build | tail -5
echo $? # → ビルドが失敗していれば 1
# zsh
setopt pipefail
npm run build | tail -5
echo $? # → 1
スクリプトの先頭に置いておくのが確実です。bashなら set -euo pipefail がよく使われる組み合わせです。
方法2: パイプを使わず、ログに落としてから読む
CIで確実にしたいなら、こちらのほうが素直です。
npm run build > build.log 2>&1
code=$?
tail -5 build.log
exit $code
終了コードを先に確保してから整形するので、パイプの挙動を気にしなくて済みます。
なぜ気づきにくいか
ビルドが失敗すると、たいてい出力にエラー文字列が出ます。目で見て確認している間は問題が表面化しません。
表面化するのは、人が見なくなったときです。
- CIが「ビルド成功」と判定してデプロイまで進む
- スクリプトが
&&で次の処理に進んでしまう - 自動化の中で、失敗が黙って通過する
私はこれを、AIエージェントにビルドを回させている最中に踏みました。出力を文字列で見て判断していたので実害は出ませんでしたが、終了コードで判定していたら、失敗を成功として扱っていました。
まとめ
- パイプの
$?は最後のコマンドの終了コード。前段の失敗は消える - 前段を見るには bash:
${PIPESTATUS[@]}/ zsh:${pipestatus[@]}(zshは小文字) - 直すなら
set -o pipefail(bash)/setopt pipefail(zsh) - CIならログに落として終了コードを先に確保するほうが確実
このサイトでは、AIに事業運営を任せながら踏んだ失敗と、その直し方を記録しています。同じ運用で他にどんな間違いが起きたかは、連載「委任の記録」にまとめています。
ナオTRYZM管理人
AIに関わるPMで、夜はAIで作って試す人。囲碁では布石が好き(何もない盤面に構想を描く時間)。 嘘をつくと自分があとで気にして引きずるタチなので、報酬が入らないプランでも一番ならそのまま書きます。 守れるルールは3つ — 盛らない。順位を売らない。試せないことも、徹底的に調べて正確に。