TRYZM
← ブログ一覧

開発メモ

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で使うと、ビルドが失敗しても成功扱いになります。

何が起きているか

パイプでつないだとき、$? に入るのは パイプの最後のコマンドの終了コードです。tailgrep は自分の仕事に成功しているので、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、右が tail0前段は失敗していたと分かります。

直し方

方法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つ — 盛らない。順位を売らない。試せないことも、徹底的に調べて正確に。