2026年8月14日 金曜日
AI時短ラボ
活用· 約26

pkillはレンダプロセスを取り逃す──chrome-headless-shellの孤児36個でWindowServerが73秒無応答になった記録

Remotionの長尺レンダを止めるとき、`pkill render.mjs` は exit 1 を返すだけでプロセスを取り逃す。macOS 15.1.1で実測したところ `node render.mjs` のプロセス名は `node` で、`-f` なしのpgrepは0件、付けると1件だった。孤児のchrome-headless-shellを36個残したままビルドを始めてWindowServerが73秒無応答になった件と、`ps aux | grep | head -20` の切り詰めを見て「走っていない」と誤断定し二重ビルドで約10分を失った件を、実測値つきでまとめる。

執筆・編集:
目次

結論から書く。長時間のレンダを止めたつもりで pkill render.mjs と打っても、プロセスは1本も死なない。macOSの pkill は既定でプロセス「名」だけを見るので、node render.mjs というプロセスの名前は node であり、render.mjs はどこにも一致しない。そして一致しなかったときの pkillエラーを出さず、終了コード1を返して黙って終わる。「No processes were matched」は画面に出ない。だから打った側は止まったと思い込み、生き残ったレンダが次のビルドと同じ出力ファイルに書き続ける。直し方は2つで、パターンを -f 付き(引数全体に一致)にすること、そしてkillの後に必ず pgrep -f "パターン" | wc -l で残0を数えることだ。以下はすべて2026-08-14に macOS 15.1.1(BuildVersion 24B91、Darwin 24.1.0 arm64、メモリ8GB)で実測した値を載せる。

3行まとめ

  1. pkill render.mjs は exit 1 を返して何も殺さないnode で起動したスクリプトのプロセス名は node であり、スクリプト名は引数側にしか無いので -f が要る
  2. 一致0件と「もともと走っていなかった」はどちらも exit 1 で、出力も同じ(無言)。だからkillの成否は件数で確認するしかない
  3. ps aux | grep ... | head -20 で「無い」と判断してはいけない。実測で該当48行に対し head -20 は目的の行を1行も含まなかった

症状

症状1:pkillが黙って空振りする

貼って検索されるのはこの並びだと思うので、実際に打った通りに置く。node faker/render.mjs を1本起動した状態での出力である。

$ ps -p 16589 -o comm=
node

$ ps -p 16589 -o command=
node faker/render.mjs

$ pgrep render.mjs
$ echo $?
1

$ pgrep -f render.mjs
16589

$ pkill render.mjs
$ echo $?
1

pkill render.mjs の後もプロセスは生きている。

$ ps -p 16589 -o pid=,command=
16589 node faker/render.mjs

-f を付けて初めて死ぬ。

$ pkill -f render.mjs
$ echo $?
0

$ pgrep -f render.mjs | wc -l
       0

**画面には何も表示されない。**エラーメッセージも「0 processes killed」も出ない。echo $? を挟まない限り、空振りと成功は見分けがつかない。

症状2:切り詰めた出力で「走っていない」と誤断定する

レンダ中のマシンで、走行中のビルドを探す典型的なワンライナーを打つ。

$ ps aux | grep -E "build.sh|remotion|render|chrome-headless" | grep -v grep | wc -l
      48

$ ps aux | grep -E "build.sh|remotion|render|chrome-headless" | grep -v grep | head -20 | grep -c "build.sh"
0

$ ps aux | grep -E "build.sh|remotion|render|chrome-headless" | grep -v grep | grep -c "build.sh"
1

build.sh は確かに走っている(3行目が1)。しかし head -20 を通した瞬間、その行は範囲外に落ちて0になる。ヘッドレスChromeのレンダラープロセスが行を埋めるからだ。この48行という数字は誇張ではない。同日、当マシンで ps aux | grep -E "Chrome|chrome" が返す行数を2回測ったところ、いずれも48行だった(ビルドを走らせていない通常のブラウジング状態で、である)。全プロセス数のほうは時点によって445〜483と振れた。head -20 は、この程度の常駐数でもう「不在の証明」として使えなくなる。

症状3:絶対パスのパターンが一致しない

これは記事を書くための検証中に、筆者自身が新たに踏んだものである。相対パスで起動したプロセスを、絶対パスのパターンで殺そうとした。

$ pgrep -f "/path/to/dir/chrome-headless-shell" | wc -l
       0

$ pkill -f "/path/to/dir/chrome-headless-shell"
$ echo $?
1

exit 1。だが実際には25本が生きていた。

$ ps ax -o command= | grep -cE "chrome-headless-shell-[0-9]+"
25

理由は単純で、そのプロセスは ./chrome-headless-shell-1 300 という相対パスのargvで起動していたため、絶対パスの文字列はどこにも存在しなかった。-f は解決済みのパスではなく、argvに載っている文字列そのものに一致する。実際に起動した形に合わせたパターンなら通る。

$ pkill -f "./chrome-headless-shell-"
$ echo $?
0

$ pgrep -f "chrome-headless-shell-" | wc -l
       0

原因

1. 既定はプロセス名で、引数は見ない

man pkill(当該マシンのフッタ表記は macOS 15.1 / October 5, 2020)の -f の説明はこうだ。

-f Match against full argument lists. The default is to match against process names.

(訳:-f は引数リスト全体に対して一致させる。既定ではプロセス名に対して一致させる。)

パターンの扱いについてはこう書かれている。

If any pattern operands are specified, they are used as extended regular expressions to match the command name or full argument list of each process.

(訳:パターンオペランドが指定された場合、それらは拡張正規表現として、各プロセスのコマンド名または引数リスト全体との一致に用いられる。)

node render.mjs の「コマンド名」は node である。render.mjs は引数側にしか無い。だから -f が無ければ永久に一致しない。同じ理屈が python prep.py にも当てはまる。

なお -x も併せて覚えておくとよい。

-x Require an exact match of the process name, or argument list if -f is given. The default is to match any substring.

(訳:-x はプロセス名(-f が与えられていれば引数リスト)の完全一致を要求する。既定では任意の部分文字列に一致する。)

2. 空振りが「エラー」として扱われない

EXIT STATUS の定義がそのまま罠になっている。

0 One or more processes were matched.

1 No processes were matched.

(訳:0 = 1つ以上のプロセスが一致した。1 = 一致したプロセスは無かった。)

つまり「パターンが間違っていて0件」と「もともと走っていなくて0件」が、同じ exit 1・同じ無言の出力になる。killが効いたかどうかを pkill 自身の見た目から判断する方法は無い。件数を別途数えるしかない。

3. pgrepは自分と祖先を除外する

これは知らないと確認の側で足をすくわれる。

-a Include process ancestors in the match list. By default, the current pgrep or pkill process and all of its ancestors are excluded (unless -v is used).

(訳:-a はプロセスの祖先を一致リストに含める。既定では、現在の pgrep または pkill プロセスとそのすべての祖先が除外される(-v が使われていない限り)。)

Note that a running pgrep or pkill process will never consider itself as a potential match.

(訳:実行中の pgrep または pkill プロセスは、決して自分自身を一致候補とみなさないことに注意せよ。)

実測でも確認した。anc.sh というスクリプトの中から自分自身を探すと0件になり、-a を付けると2つのPIDが出る。

$ pgrep -f anc.sh          # anc.sh の中から実行
$ echo "件数=0"
件数=0

$ pgrep -af anc.sh         # -a で祖先を含める
16637
16640

「残っていないことの確認」をビルドスクリプト自身の中から行うと、自分の系統は数から消える。確認は対象と無関係な場所から打つほうが安全である。

4. Claude Codeの自殺ガードはmacOSでは働かない

レンダをAIエージェント経由で止める場合に効いてくる話。Claude Codeの公式ドキュメントには pkill pattern matches the Claude Code process という項があり、こう書かれている。

A pkill command in a Bash tool call used a pattern, typically with -f, that matches the Claude Code process itself, so Claude Code refuses the command instead of letting it end the session. Claude Code tests the pattern with pgrep before running pkill and refuses when its own process ID is in the result. The check runs on Linux only; on macOS, pkill runs unmodified. Before v2.1.214, the command ran, and a matching pattern killed the Claude Code session mid-turn.

(訳:Bashツール呼び出し中の pkill コマンドが、典型的には -f を伴うパターンで、Claude Code のプロセス自体に一致した。そのため Claude Code は、セッションを終了させるのではなくコマンドを拒否する。Claude Code は pkill を実行する前に pgrep でパターンを試し、自分自身のプロセスIDが結果に含まれる場合は拒否する。このチェックが走るのは Linux のみで、macOS では pkill は変更されずに実行される。 v2.1.214 より前は、コマンドはそのまま実行され、一致するパターンはターンの途中で Claude Code のセッションを終了させた。)

拒否されたときに出る文字列も明記されている。

pkill: refusing to run — this pattern matches the Claude CLI process (PID 12345). Narrow the pattern, or target your own children with `pkill -P $$ ...`.

そして、その拒否がどこに現れるかについて。

The refusal appears in the Bash tool result rather than as a banner in your terminal, and Claude usually adjusts the command on its own.

(訳:この拒否はターミナルのバナーとしてではなく、Bashツールの結果として現れる。そして Claude は通常、自分でコマンドを調整する。)

要するに、macOSで作業している限りこのガードは無いという前提で広いパターンを打つ必要がある。当マシンの Claude Code は 2.1.232 で、ドキュメントが言う v2.1.214 より新しいが、macOSなので上記の通りチェック自体が対象外である。

対処

-f を付け、起動時の書き方に合わせる

pkill -f "motion_pipeline/node_modules/.remotion/chrome-headless-shell"
pkill -f "render.mjs"

公式ドキュメントの「What to do」もパターンを絞る方向を勧めている。

Narrow the pattern so it matches only the intended process, for example the full path of the target binary rather than a short substring

(訳:意図したプロセスだけに一致するようパターンを絞る。たとえば短い部分文字列ではなく、対象バイナリのフルパスを使う。)

ただし症状3の通り、フルパスは「起動時のargvがフルパスだったとき」しか効かない./build.sh で起動したものは ./build.sh でしか捕まらない。迷ったら先に pgrep -lf でargvの実物を見るのが早い。

$ pgrep -lf "chrome-headless-shell"
18343 ./chrome-headless-shell-1 90
18345 ./chrome-headless-shell-2 90

② killしたら必ず件数で残0を確認する

これが本記事の主題である。

pkill -f "render.mjs"
sleep 1
pgrep -f "render.mjs" | wc -l    # ← 0 を目で見るまでkill完了とみなさない

pkill の exit code を見るのでは足りない。exit 1 は「パターンが違った」かもしれないからだ。残件数0という別の観測を取る。

しつこく残る場合はPIDループでSIGKILLに落とす。

for p in $(pgrep -f "chrome-headless-shell"); do kill -9 "$p"; done
pgrep -f "chrome-headless-shell" | wc -l

③ 「無い」の判定に head を使わない

# 悪い:切り詰めで目的の行が消える
ps aux | grep -E "build.sh|remotion|render" | head -20

# 良い:件数で取る
pgrep -f "build.sh" | wc -l

不在を証明したいときに出力を切ってはいけない。head は「先頭20行に無かった」しか言えず、「無い」は言えない。

④ ビルド前に孤児を掃除する

レンダが完走した後もヘッドレスChromeが残ることがある。次のビルドを始める前に落とす。

pkill -f "motion_pipeline/node_modules/.remotion/chrome-headless-shell"
pgrep -f "chrome-headless-shell" | wc -l    # 0 を確認してから起動

⑤ 自分の子だけを狙う手もある

公式ドキュメントのもう一つの推奨。

To stop processes started by the current shell, use pkill -P $$ with the pattern, which limits the match to the shell’s own child processes

(訳:現在のシェルが開始したプロセスを止めるには、パターンとともに pkill -P $$ を使う。これは一致をそのシェル自身の子プロセスに限定する。)

ただしビルドをバックグラウンドで投げて別のターンから止める運用だと、止める側のシェルは既に別プロセスなので $$ は効かない。使えるのは同一シェル内で完結する場合に限られる。

踏んだ記録

①〜③は筆者自身の制作環境(Remotionベースの動画レンダパイプライン、MacBook Air・メモリ8GB)で起きたものである。④は本記事の検証中に、同じマシンで起きた。

① 孤児36個でGUIセッションが全滅(2026-07-11)

過去ビルドの孤児 chrome-headless-shell36個、7月3日から放置されたまま残っていた。その状態でメモリ8GBのマシンで新しいビルドを開始したところ、WindowServerが73秒無応答になり、watchdogに殺されてGUIセッションが全滅した。作業中だったセッションも巻き添えになった。8日間気づかなかったのは、レンダ完走後に残件数を数える習慣が無かったからで、完走=掃除済みだと思い込んでいた。

② kill未検証で二重書き込み、mp4が全損(2026-08-10)

台本の日付を直すためにビルドを pkill で止めた。そのとき残存確認を飛ばした。前ビルドの render.mjs が1本生き残っており、新しいビルドと同じ出力ファイルに二重書き込みした。ビルドは完成表示のまま終わったが、出来上がったmp4は映像のNALが破損していて再生できなかった。フル作り直しになり、約35分の損失。悪質なのは、画面上は最後まで正常に見えていたことだ。壊れた出力は完成扱いで納品直前まで進んだ。

③ head -20 の切り詰めを見て二重ビルド(2026-08-13)

走行中のビルドを探すのに ps aux | grep -E "build.sh|remotion|render|chrome-headless" | head -20 を打った。ヘッドレスChromeのレンダラー行で埋まって build.sh の行が表示範囲外に落ちていたのに、その出力を見て「走っていません」と断定し、同じ台本のビルドをもう1本起動した。2本が同一の _voice.mp4 に書き込み、さらに2本目の前処理が1本目のレンダ中に timeline.json と音声ディレクトリを上書きした。どちらの出力も信用できず両方破棄、約10分の損失。このときマシンはswap 17GBまで膨らんでいた。

④ 本記事の検証中に、絶対パスのパターンで25本を取り逃した(2026-08-14)

症状3はこの日に新しく踏んだものである。検証用の後片付けとして pkill -f "<絶対パス>/chrome-headless-shell" を実行し、exit 1 が返った。相対パスで起動していたので一致0件だったのだが、その時点では「もう死んでいる」と読んだ。実際には25本が生きたままで、直後に ps で数えて気づいた。exit 1 を「掃除済み」と読むという、この記事が警告している当のミスを、記事を書きながらやった。件数で数え直していなければそのまま放置していた。

4件とも共通しているのは、killや不在の判定を「見た目」で済ませたことだ。①は完走したから消えたはず、②はpkillを打ったから止まったはず、③は出力に無いから走っていないはず、④はexit 1だから居ないはず。4つとも別の観測(件数)を取れば10秒で否定できた。

正直な但し書き

  • man pageの「19文字」の挙動は再現できなかった。 man pkill のEXAMPLESには Without -f names over 19 characters will silently fail:(訳:-f が無い場合、19文字を超える名前は黙って失敗する)というキャプションがある。chrome-headless-shell は21文字なのでこれに該当するはずだが、当環境で36文字のプロセス名を作って試したところ、先頭10・16・17・19・20・21・26・30・36文字のどのプレフィックスでも、また末尾側の部分文字列でも -f 無しで一致した。0件になる条件を特定できていない。本記事は「21文字だから pkill が効かない」とは主張していない。実測で効かなかったのは node render.mjs のように名前と引数が別のケースであり、これは名前の長さとは別の原因である。
  • 検証に使ったのは /bin/sleep へのシンボリックリンクで、本物の chrome-headless-shell ではない。 実際のヘッドレスChromeは多数の子プロセスを生成し、親を殺しても子が残る挙動があり得る。本記事の再現は「pkillのパターン一致の性質」を示すもので、Chromeのプロセスツリー全体の挙動を検証したものではない。
  • pkill -f がClaude Code自身を殺すかどうかはテストしていない。 公式ドキュメントは「macOSでは pkill は変更されずに実行される」と書いており、一方でmacOSのman pageは「自分自身と祖先は既定で除外される」と書いている。この2つが実際にどう噛み合うか(macOS上でエージェント経由の広い pkill -f がセッションを落とすのか落とさないのか)は、セッションを壊す実験になるため確認していない。広いパターンは避ける、という運用で回避している。
  • 件数が1回だけ合わなかった。 25本起動した状態で pgrep -f が26件を返した場面が1度あった。原因を特定できず、5本で再試行したときは5件で一致し、再現しなかった。**この26という数字に説明を付けられないので、本文には使っていない。**件数が想定と合わないときは -lf でコマンドラインを目視するしかない。
  • 73秒・36個・35分・10分・17GBは、いずれも当時の作業記録に残した値で、本記事のために再測定したものではない。 再測定できるものではない(事故の再現になる)。一方、-f の有無による0件/1件、48行、exit code、祖先除外の挙動は2026-08-14に当該マシンで実行して得た値である。
  • メモリ8GBという条件が効いている。 ①のWindowServerクラッシュは、孤児36個そのものより「8GB機で孤児が居座った状態から更にレンダを始めた」ことの結果である。潤沢なメモリのマシンで同じ本数の孤児が同じ結果を招くとは限らない。
  • Linux・他のBSDでの挙動は未検証。 引用したman pageは当該マシンのもので、フッタは macOS 15.1 / October 5, 2020 である。GNU procps の pkill はオプションの意味や既定が異なる部分があり、本記事の実測値をそのまま持ち込めない。

出典

  • Claude Code Docs「Errors」セクション pkill pattern matches the Claude Code processhttps://code.claude.com/docs/en/errors(2026-08-14にGETで取得、HTTP 200)。macOSでpgrepによる事前チェックが行われないこと、拒否時の文字列、pkill -P $$ の推奨はすべてこのページの原文から引用した
  • pkill(1) / pgrep(1) man page — 引用は当該マシンで man pkill を実行して取得(フッタ表記 macOS 15.1 / October 5, 2020)。オンライン版 https://keith.github.io/xcode-man-pages/pkill.1.html も2026-08-14にGETで取得(HTTP 200)し、本記事が引用した7か所(-f / -x / -a の説明、自分自身を一致候補としない旨、拡張正規表現の扱い、EXIT STATUS、19文字のキャプション)がすべて同じ文言で存在すること、日付表記も同じ October 5, 2020 であることを確認した
  • 実測環境 — macOS 15.1.1(BuildVersion 24B91)、Darwin Kernel Version 24.1.0 arm64、メモリ8GB、Claude Code 2.1.232、Node.js v25.9.0。本文中のコマンド出力はすべて2026-08-14に同マシンで実行した結果である
シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事

Claude Codeがエラーで動かない時の切り分け手順──400・401・403は「認証の優先順位」を疑うの記事画像
活用08.14読了22

Claude Codeがエラーで動かない時の切り分け手順──400・401・403は「認証の優先順位」を疑う

出典 ─ Claude Code公式ドキュメント
AI時短ラボ
活用08.14読了23

ffmpegのエンコード指定を出力ファイル名より後ろに置くと効かない──96000Hzのまま連結して尺が43秒に化けた話

出典 ─ ffmpeg Documentation -
AI時短ラボ
活用08.14読了31

RemotionのLoopはコンポジション絶対フレームで位相を計算する──カード内の動画クリップが登場した瞬間に終盤から再生される原因と直し方

出典 ─ <Loop> | Remotion公式ドキュ
Claude Codeとは──コード生成に留まらない「仕事を渡せる」AIエージェントの記事画像
活用07.17読了6

Claude Codeとは──コード生成に留まらない「仕事を渡せる」AIエージェント

出典 ─ Claude Code の概要(Anthro
Claude Codeで初心者がゲームを作る手順──未経験から公開までの4ステップの記事画像
活用07.16読了6

Claude Codeで初心者がゲームを作る手順──未経験から公開までの4ステップ

出典 ─ Claude Code の概要(公式ドキュメ
Remotionで長い動画のレンダリングがクラッシュする──分割レンダとffmpeg無劣化連結で完走させる手順の記事画像
活用07.16読了7

Remotionで長い動画のレンダリングがクラッシュする──分割レンダとffmpeg無劣化連結で完走させる手順

出典 ─ npx remotion render |
AIコーディングアシスタント比較──Copilot/Cursor/Claude Code【2026年】の記事画像
活用06.19読了8

AIコーディングアシスタント比較──Copilot/Cursor/Claude Code【2026年】

出典 ─ Scrimba※2026-08-14時点でリンク切れ(404)
AIプログラミング 初心者の始め方【2026年ロードマップ】の記事画像
活用06.19読了10

AIプログラミング 初心者の始め方【2026年ロードマップ】

出典 ─ The 9 best AI coding t