アルアカ - Arcadia Academia

Arcadia Academiaは「エンジニアリングを楽しむ」を合言葉に日本のデジタル競争力を高めることをミッションとするテックコミュニティです。

Claude Codeのclaude -pでflockが止まる時はfcntlロックに置き換える

Featured image of the post

「夜間バッチでclaude -pを使って複数の処理を順番に走らせたい。二重起動を防ぐためにflockを使おうとしたら、実行が止まって先に進まない」「エラーメッセージが出ないまま固まる」「サンドボックス環境だと使えるコマンドとそうでないコマンドがあるらしいが違いが分からない」——Claude Codeのヘッドレス実行(claude -p)で排他制御を入れようとして詰まる人は少なくありません。この記事では、症状の切り分け方と、flockの代わりにPythonのfcntlで自作ロックスクリプトを用意する具体的な回避策を示します。

💡
30秒で要点
・claude -p(ヘッドレス実行)のサンドボックス下ではflockコマンドが許可照合で素通りしないことがある
・症状はエラーメッセージなしで処理が止まる・進まない、が典型
・対処はflockを諦め、Pythonのfcntl.flock()を使った小さな自作ロックスクリプトに置き換える
・同じロックファイルを複数プロセスから取り合う排他制御全般に応用できる

目次を開く

症状:ヘッドレス実行でflockが機能しない

夜間cronなどからclaude -pを使い、同じロックファイルを取り合って処理を直列化したい場面を想定します。

# 重い処理を1つずつ順に実行したい想定
flock -n /tmp/render.lock -c "claude -p '重い処理を実行して' --model sonnet"

このコマンド自体は通常のシェルでは動きますが、サンドボックス実行環境(Claude Codeのようにコマンドを許可リストと照合してから実行するタイプのツール)の中で叩くと、以下のような形で引っかかることがあります。

  • コマンドが許可リストに無い・複合コマンドと判定され、実行前に止まる
  • エラーメッセージが出ず、処理そのものが進まない

原因:許可照合はコマンド単位で、flockの用法と相性が悪い

サンドボックス型の実行環境は、実行しようとするシェルコマンドを事前に許可リスト(allowlist)と突き合わせて許可・拒否を決めます。flock -c "..."のように別のコマンドを丸ごと引数として渡す形は、外側のflockだけでなく中に埋め込まれたコマンドも合わせて解釈されるため、単純な文字列一致の許可照合とかみ合わないことがあります。

これはflockに限らず、xargs経由の実行やsh -c "..."のように「コマンド文字列を丸ごと渡して実行する」系のコマンド全般で起こりえます。

対処:fcntlベースの自作ロックに置き換える

flockコマンドを使わず、同じ排他制御をPythonの標準ライブラリfcntlで自前実装します。これなら許可照合の対象は「python3 lock.py ...」という単純な1コマンドで済みます。

# lock.py — ロックファイルを取得してから渡されたコマンドを実行する
import fcntl
import subprocess
import sys

lock_path = sys.argv[1]
cmd = sys.argv[2:]

with open(lock_path, "w") as f:
    fcntl.flock(f, fcntl.LOCK_EX)  # 他プロセスがロック中ならここで待つ
    result = subprocess.run(cmd)

sys.exit(result.returncode)

呼び出し側は次のようになります。

python3 lock.py /tmp/render.lock claude -p "重い処理を実行して" --model sonnet

ロック取得に失敗させたくない(待たずに諦めたい)場合はfcntl.LOCK_EX | fcntl.LOCK_NBを使い、BlockingIOErrorを捕まえて分岐します。

try:
    fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
    print("already running, skip")
    sys.exit(1)

確認:ロックが効いているかを手元で試す

本番の無人実行に組み込む前に、2つのターミナルから同じロックファイルを指定して手元で確認しておきます。

# ターミナル1: 10秒ロックを保持
python3 -c "
import fcntl, time
f = open('/tmp/test.lock', 'w')
fcntl.flock(f, fcntl.LOCK_EX)
print('locked')
time.sleep(10)
"
# ターミナル2: 同時に実行すると10秒待ってから通る
python3 -c "
import fcntl
f = open('/tmp/test.lock', 'w')
fcntl.flock(f, fcntl.LOCK_EX)
print('acquired after wait')
"

ターミナル2が待たされて出力が遅れれば、排他制御は正しく機能しています。

まとめ

  • サンドボックス型の実行環境(Claude Codeのヘッドレス実行など)では、flock -c "..."のようにコマンド文字列を丸ごと渡す用法が許可照合と相性が悪く、素通りしないことがある
  • 症状はエラーメッセージを伴わず処理が進まない形で現れる
  • 対処はflockを諦め、Pythonのfcntl.flock()で小さな自作ロックスクリプトを用意し、単純な1コマンドとして呼び出す
  • 本番組み込み前に、2つのターミナルから同じロックファイルを叩いて排他制御が効いているか確認する

よくある質問(FAQ)

Q. flock以外のコマンドでも同じことが起きますか?

A. 起こりえます。sh -c "..."やxargs経由など、「コマンド文字列を丸ごと別のコマンドに渡して実行する」用法全般が対象になりやすい構造です。許可照合がコマンド単位で行われる実行環境では、複合コマンドをできるだけ単純な1コマンドに分解しておくと安全です。

Q. ロック取得を待たずに諦めたい場合はどうすればいいですか?

A. fcntl.LOCK_EXの代わりにfcntl.LOCK_EX | fcntl.LOCK_NBを指定すると、ロックが取得できない時に待たずBlockingIOErrorを送出します。これを捕まえてスキップ処理に分岐させます。

Q. Windowsでも同じ方法は使えますか?

A. fcntlはUnix系OS専用のモジュールで、Windowsには存在しません。Windows環境で同じく排他制御をしたい場合はmsvcrt.locking()など別のAPIを使うか、WSL上のLinux環境で実行してください。

Q. ロックファイルの置き場所に決まりはありますか?

A. 複数プロセスから同じパスを参照できればよく、/tmp配下で問題ありません。ただし再起動でファイルが消えても実害はない設計にしておく(起動時に無ければ自動作成される想定で書く)のが安全です。

プログラミング学習でお悩みですか?

現役エンジニアがあなたの学習をマンツーマンでサポートします。

  • 学習の進め方がわからない
  • ポートフォリオの作り方を知りたい
  • 現場で使える技術を学びたい
まずは30分の無料相談

相談は完全無料・オンラインで気軽に

あなたを爆速で成長させるメンタリングプログラムはこちら

メンタープログラムバナー

学習・開発のお悩みは現役エンジニアに相談

メンタープログラムの詳細を見る

エンジニアの基礎学習ゲーム

プログラミング学習支援

無料相談はこちら