Claude Media
Claude CodeがCentOS 7でCPUを使い続ける原因とDockerでの切り分け

Claude CodeがCentOS 7でCPUを使い続ける原因とDockerでの切り分け

CentOS 7(カーネル3.10)ホストのDockerでself-hosted-runnerを動かすと、セッションが待機中もCPUを約2.3コア使い続けます。報告されている原因と、手元で確かめる手順、現実的な逃げ道をまとめます。

古いLinuxホストの上のDockerコンテナでClaude Codeのセルフホストセッションを動かしていて、待機中でもCPUが2コア分ほど張り付くなら、ホストのカーネルが3.10系かどうかを最初に見ます。GitHubのissue #99797には、CentOS 7(カーネル3.10)ホストで再現した報告があります。セッションは動き続けますが、CPUだけが無駄に使われます。

この記事は、その症状の見え方、報告されている原因の連鎖、手元で切り分けるコマンド、そして現実的な逃げ道を順に扱います。issueは2026-10-05に立てられたもので、回避用の設定は示されていません。最新の状況はissue #99797のページで確かめられます。

どんな症状が出るのか

報告された環境は、Claude Code 2.1.289のネイティブインストーラー版を、claude self-hosted-runner(--capacity 1)でDockerコンテナの中から動かす構成です。コンテナはUbuntu 22.04で、rootではないユーザーで動きます。ホストはCentOS 7でカーネルは 3.10.0-514.6.1.el7.x86_64、Dockerは23.0.1です。

セッションのプロセス(claude --print --sdk-url … で起動される子プロセス)に出た数字は次のとおりです。

項目報告された値
プロセス全体のCPU使用率報告された値約225%
メインスレッド報告された値100%(22分11秒の稼働で21分50秒のCPU時間)
Bun Pool スレッド報告された値8本が各13%前後
自発的コンテキストスイッチ報告された値22分間で約55万6千回
親の claude self-hosted-runner報告された値0.2%

CPUを食うのはセッションの子プロセスだけで、親のRunnerは静かです。セッションの応答自体は正常に返るため、使い心地では気づきにくく、ホストの負荷やクラウドの請求で初めて見つかります。

straceで見えるのは「/」の無限ループ

報告者は strace -f -c を5秒間かけています。呼び出し回数が多い順に、futex が約24.8万回、write が約4.96万回(すべてイベントループのeventfd宛て)、epoll_pwait が約2.86万回です。openat・readlink・newfstatat・close はいずれも約2.48万回で、readlink はすべて失敗しています。

プールスレッドが繰り返しているのは、次の5手順です。

openat(AT_FDCWD, "/", O_RDONLY|O_CLOEXEC|O_PATH) = 13
readlink("/proc/self/fd/13", ..., 4096)          = -1 EACCES (Permission denied)
close(13)
write(5, "\1\0\0\0\0\0\0\0", 8)                  # イベントループを起こす
newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|0755, ...}, 0) = 0

realpath("/") が毎秒およそ9,000回、しかも常に / に対して呼ばれています。パスを解決したいのではなく、失敗した同じ問い合わせが止まらず回っている形です。

原因は3つの条件が重なること

報告者の分析では、原因は次の連鎖です。

手順

CPUが回り続けるまでの流れ

  1. 1

    セッションのプロセスが自分を非dumpableにする

    非dumpableの間、/proc/<pid>/fd の所有者は root:root、権限は 0500 になります。同じコンテナ内の親Runnerなど他のプロセスのエントリは、セッションのユーザーが所有しています。

  2. 2

    カーネル3.10では、メインスレッド以外が自分のfdを読めない

    この環境のカーネルでは、非dumpableなプロセスが自分の /proc/self/fd を読めるのはメインスレッドだけです。同じプロセスの別スレッドは EACCES になります。

  3. 3

    Bunの非同期realpathがスレッドプールで失敗する

    Bunの非同期 realpath はスレッドプール上で、open(O_PATH) と readlink("/proc/self/fd/N") の組でパスを解決します。そのため、非dumpableの間は必ず EACCES で失敗します。同期版はメインスレッドで動くので成功します。

  4. 4

    失敗した呼び出しが、上限も待ちもなく再試行される

    Claude Code側のどこかが、失敗した解決を制限なしで繰り返します。報告者もこの呼び出し元は特定できておらず、「何かが再試行している」と書いています。

なお、Claude Codeのネイティブ版が内蔵するBunは1.4.3です。報告者はBun単体でも再現しており、使ったのは公開済みの最新版1.4.2です。そのため、Bunの非同期 realpath が失敗したときの代替経路(パスを要素ごとにたどる方法)は、Bun側の課題としても挙げられています。

4つ目の呼び出し元は未特定で、1〜3つ目もカーネル3.10での観察です。報告者は、より新しいカーネルでは試していません。新しいカーネルで同じ条件が成立するかどうかは、issueからは分かりません。

手元のホストで確かめる手順

同じ症状かどうかは、Claude Codeのセッションを使わずに確かめられます。順番に3段階です。

1. カーネルとCPUの張り付きを見る

ホスト側で次を実行します。

uname -r
top -H -p "$(pgrep -f 'claude --print' | head -1)"

uname -r が 3.10.0-…el7 の形なら条件に当たります。top -H はスレッド単位の表示で、メインスレッドが100%近く、Bun Pool が複数本とも動いていれば報告と同じ見え方です。

2. fdディレクトリの所有者を見る

コンテナの中で、セッションのプロセスの fd ディレクトリを確認します。

pid="$(pgrep -f 'claude --print' | head -1)"
ls -ld /proc/"$pid"/fd
grep ctxt /proc/"$pid"/status

root:root で権限が dr-x------ なら、非dumpable化と一致します。2行目は、自発的・非自発的のコンテキストスイッチの累計です。数秒空けて2回測り、増え方が桁違いに大きければループの状況証拠になります。

3. カーネルの挙動だけを再現する

issueには、Bunもセッションも使わずにカーネルの挙動だけを見るPythonの再現コードが載っています。コンテナ内で、セッションと同じ非rootユーザーとして動かします。

python3 - <<'EOF'
import ctypes, os, threading
ctypes.CDLL(None).prctl(4, 0, 0, 0, 0)   # PR_SET_DUMPABLE = 0
def t(where):
    fd = os.open("/", os.O_RDONLY)
    try: print(where, os.readlink(f"/proc/self/fd/{fd}"))
    except OSError as e: print(where, e)
t("main  ")
threading.Thread(target=t, args=("thread",)).start()
EOF

報告では、メインスレッドは / を返し、別スレッドは [Errno 13] Permission denied になります。手元でも2行目が Permission denied になれば、カーネルがこの挙動をする環境です。新しいカーネルのホストで同じコードを動かして2行とも / が返れば、少なくともカーネル側の条件は外れています。

逃げ道はどこにあるか

報告者が採らなかった2案

issueは、試せなかった方法を2つ挙げています。

  • CAP_DAC_READ_SEARCH を付ける: スレッドが /proc/self/fd をたどれるようになります。ただし open_by_handle_at も使えるようになり、コンテナ脱出の既知の経路になるため、報告者は付与しないと判断しています
  • ホストのカーネルを上げる: 共有のHPCノードでOSの入れ替えと再起動が要るため、当面は無理だという事情です

報告者は「セッションのプロセスをdumpableに保つ設定」や「この解決を避ける設定」があれば足りる、と尋ねています。issueの本文には、こうした設定は示されていません。

設定や運用で被害を抑える

原因そのものは外せなくても、CPUの無駄を上限で抑える手はあります。

手段効くところ注意
Dockerの --cpus やKubernetesのCPU limit効くところ1コンテナが使える上限を固定する注意上限に達したコンテナはカーネルに絞られるが、プロセスは止まらずセッションは続く
--kill-session-after-min効くところ暴走セッションの寿命に上限をかける注意上限に達したセッションは猶予の後に終了する(v2.1.260以降は猶予が既定15分)
--release-idle-session-min効くところアイドルが続いたセッションのスロットを解放する注意解放したセッションがCPUを使い続けるかどうかは、docsに記載がない
--capacity 1 と使い捨てコンテナ効くところセッションごとにコンテナを作り直し、張り付きを持ち越さない注意Runnerの強化指針に沿った構成でもある

セルフホスト環境のdocsでは、1セッションあたりの出発点を「CPU request 2・limit 4」としています。待機中だけで約2.3コアを使うセッションは、この想定の半分以上を何もせずに消費します。ビルドやテストの余裕が、その分だけ減ります。

--capacity を2以上にしているなら、同じ症状は1セッションごとに出ると見るのが素直です。報告の計測は1セッションだけなので、本当に掛け算になるかは手元で測るまで分かりません。

ホストを替えるのが確実

Claude Codeの動作環境として挙がっているLinuxは、Ubuntu 20.04以降・Debian 10以降・Alpine Linux 3.19以降です。CentOS 7は一覧にありません。システム要件の確認はClaude Codeのシステム要件にまとめています。

カーネルを上げられないなら、Runnerだけを対応OSの別ホストに移し、CentOS 7側はGitホストやデータ置き場として残す分担が考えられます。Runnerの設定そのものはself-hosted-runnerの導入手順の内容がそのまま使えます。

似た症状との見分け

コンテナの中でClaude Codeがうまく動かない原因は、他にもあります。入口になるメッセージで分けられます。

  • bwrap: Can't mount proc … Operation not permitted が出て、sandbox内のコマンドが失敗する → 非特権Dockerの権限の問題で、enableWeakerNestedSandboxとbwrapPathが扱う症状です
  • エラーは出ず、セッションも動くが、CPUだけが張り付く → この記事の症状です。readlink("/proc/self/fd/…") が EACCES で並ぶかをstraceで確かめます
  • コンテナの起動や認証が通らない → Dockerでの実行手順を扱うClaude Code Docker実行ガイドで、認証の持ち越しとヘッドレス実行を先に見ます

straceでは readlink が成功しているのにCPUが高いなら、別の原因です。この記事の症状とは切り分けます。

報告に添えると役立つ情報

issueが has repro ラベル付きでopenのままなので、手元でも再現したなら、同じissueに環境を足す価値があります。載せる項目は次のとおりです。

  1. uname -r の出力と、ホストのディストリビューション名
  2. claude --version(報告はv2.1.289)
  3. コンテナのユーザーと、付与したcapability・no-new-privileges の有無
  4. 上のPythonスニペットの出力
  5. 5秒間の strace -f -c の上位行

報告者の環境は --cap-drop ALL に CHOWN・SETUID・SETGID だけを戻し、--security-opt no-new-privileges とDockerの既定seccompを使う構成でした。capabilityの付け方によって結果が変わるかは、issueには書かれていません。ここを自分の環境で変えて試した結果は、切り分けの材料になります。

まとめ

症状の核は、非dumpableなプロセスのスレッドが /proc/self/fd を読めないカーネルで、非同期の realpath("/") が失敗し続けることです。追えているのはカーネル3.10のCentOS 7という1つの環境で、原因の呼び出し元は未特定のままです。

古いホストでセルフホストRunnerを回す予定があるなら、導入前に上のPythonスニペットを1回動かしておくと、後から高いCPU使用率に気づく事態を避けられます。別スレッドの行でも / が返るホストなら、この症状の条件から外れます。

この記事を共有:XはてブLinkedIn