Claude Media
Claude Codeのfindがntfs3のジャンクションで止まらない原因と対策

Claude Codeのfindがntfs3のジャンクションで止まらない原因と対策

LinuxでWindowsドライブをntfs3でマウントすると、Claude Codeのfind(bfs)がジャンクションをたどり続けてメモリを使い切る報告があります。原因とGNU findとの違い、止め方を示します。

find / -name 何かを走らせたら、いつまでも終わらずメモリが増え続けた。Windowsと共存するLinux機で、そんな報告が上がっています。

報告されているのは、Claude CodeのBashツールのfindが、ntfs3でマウントしたWindowsドライブ上のジャンクションを延々とたどる現象です。GNU findでは同じ場所で止まります。報告者の環境では、18分で実メモリとスワップを使い切り、カーネルのOOM killerがプロセスを落としました。

この記事は、anthropics/claude-codeのissue #104497(起票は2026-10-23)の内容をもとに、原因の切り分けと、今すぐできる予防策をまとめます。

Claude CodeのfindはGNU findではない

macOS・Linux・WSLのClaude Codeでは、Bashツールの中のfindとgrepが、Claude Code自身の実行ファイルを呼び直す形で動きます。findの実体は組み込みのbfs、grepはugrepです。仕組みはGlobとGrepが無い理由の解説で詳しく扱っています。

issueによると、findはおおむね次の形のシェル関数に置き換わっています。

exec -a bfs "$CLAUDE_CODE_EXECPATH" -S dfs -regextype findutils-default ...

つまり、Claudeがfindと打つと、/usr/bin/findではなくbfs 4.1.1が動きます。ふだんは意識しない違いですが、ファイルシステムが変わった挙動を返すと、この差がそのまま現れます。

何が起きていたのか — d_typeはディレクトリ、lstatはシンボリックリンク

引き金は、Windowsのジャンクション(ディレクトリ結合)です。報告者の環境には、自分の親ディレクトリを指すジャンクションがありました。

Users/<user>/AppData/Local/Application Data
  -> ./../../../../Users/<user>/AppData/Local

ntfs3は、このエントリを調べる方法によって別の種類として返しました。

調べ方返ってきた種類
lstat()(statコマンド)返ってきた種類シンボリックリンク
ディレクトリ読み出し時のd_type(Pythonのos.scandir)返ってきた種類ディレクトリ(DT_DIR)

bfsはd_typeを信用し、これをディレクトリとして中に入ります。-P(シンボリックリンクをたどらない指定)を付けても同じでした。結果としてApplication Data/Application Data/Application Data/…と、自分自身の中へ潜り続けます。

GNU findとの比較(報告者の計測)

issueには、読み取り専用でマウントしたドライブでの計測が載っています。-S dfsはshimの呼び出しと同じ指定です。

くらべる

同じマウントで走査した結果

止まらない

Claude Code同梱のbfs 4.1.1

15秒で62.7万行、パスは65階層まで潜り、まだ実行中でした。プロセスの匿名メモリは毎秒35〜50MBずつ増え、20秒で1.1GB、75秒で2.6GBに達しています。

止まる

GNU find 4.10(/usr/bin/find)

629,886件を17秒で走査して終了。メモリは18MBでした。

-maxdepth 4を付けた検索でも、bfsはApplication Dataを入れ子に出力し続け、GNU findは1回だけ出力して先へ進みませんでした。

なぜGNU findは止まるのか

bfsの作者はissueのコメントで、これはntfs3側の不具合に見えると答えています。カーネルには同種の問題を直すコミットがありましたが、取りこぼしがある可能性が指摘されています。

ntfs3がDT_DIRを返してしまう条件も、作者がClaudeに尋ねた回答として共有されています。ジャンクションは「再解析ポイントを持つディレクトリ」なので、親ディレクトリの索引に保存された$FILE_NAMEの複製では最初ディレクトリとして見えます。DT_LNKに直されるのは、次の条件が揃ったときだけです。

  1. 索引に複製された再解析タグ(extend_data)が0でない
  2. ntfs_iget5()が成功する(失敗するとディレクトリ扱いのまま残る)
  3. 対象がディレクトリ自身を指すだけの項目ではない

Windowsはこの複製を常に最新にはしないため、ここが古いままだとディレクトリとして返ります。出所はAIの回答なので、確定した仕様ではなく推測として読んでください。

GNU findが止まる理由については、作者が「GNU findもおそらくd_typeを信用するが、全モードで循環検出をするので、今回のようなループは捕まる」という見立てを書いています。こちらも作者の推測です。

実害 — 何が巻き込まれたか

報告された事故の流れは、次のとおりです。

  1. エージェントがfind / -name <file> -newer <file> 2>/dev/null | headを実行した
  2. 一致する行が1つも出ないので、headは入力を受け取れず、パイプを閉じなかった
  3. bfsが18分で33GBのRSSと7GBのスワップに到達した
  4. RAMと32GBのスワップを使い切り、カーネルのOOM killerが落とした

低メモリを検知してバックグラウンドシェルを止める仕組みが、強制終了の7秒前に働きました。ただ間に合わず、他の4セッションのバックグラウンドタスクまで止まっています。

しかも、プロセス名はカーネルのOOMログで2.1.303(Claude Codeのバージョン)と出ます。exec -a bfsはargv[0]を変えますが、commは変えないためです。ログのどこにもfindは現れず、原因の追跡に時間がかかったと書かれています。

メモリリークが原因のOOMとの見分け方は、Claude Codeのメモリリークで使用量が急増しOOM Killされる原因と対処も参考になります。

自分の環境が対象かを確かめる

次の2つがそろうと、この現象に当たる可能性があります。

  • LinuxまたはWSLで、Claude Codeのfindがbfsになっている
  • ntfs3ドライバでWindows形式のドライブ(ジャンクションを持つ)をマウントしている

報告者の環境は、Ubuntu 26.04、カーネル7.0.0、ntfs3、Claude Code 2.1.309で、2.1.303でも同じ事故が起きています。ほかの版・ほかのカーネルで再現するかは、issueからは分かりません。

マウント方式の確認と、d_typeとlstatの食い違いの確認は、次のとおりです。issueの手順をそのまま短くしたものです。

# ntfs3でマウントされているか
mount | grep ntfs3
 
# lstat側の種類(symbolic link と出れば対象のジャンクション)
stat -c %F "<mnt>/Users/<user>/AppData/Local/Application Data"
 
# d_type側の種類([True] ならディレクトリとして返っている)
python3 -c 'import os;print([e.is_dir(follow_symlinks=False) for e in os.scandir("<mnt>/Users/<user>/AppData/Local") if e.name=="Application Data"])'

statがsymbolic link、Pythonが[True]なら、食い違いが再現しています。<mnt>と<user>は自分のパスに置き換えてください。

今すぐできる予防策

1. Windowsドライブをマウントしない、またはfindの範囲を狭める

報告者が最終的に採った手の一つは、Windowsドライブをマウントしないことです。必要なときだけ読み取り専用で手動マウントする運用にすると、Claudeのfindが入り込む余地が減ります。

マウントを残すなら、find /のようにルートから走査させない運用が要ります。-xdevは別のファイルシステムに入らない指定で、ルートから探してもマウントしたドライブには入りません。

2. メモリ上限を付ける

CLAUDE_CODE_TOOL_MEMORY_LIMITは、LinuxとWSLでBash・PowerShell・Monitorツールのコマンドが使えるメモリを制限する環境変数です。4Gのようにサイズを書き、v2.1.233以降で使えます。

{
  "env": {
    "CLAUDE_CODE_TOOL_MEMORY_LIMIT": "8G"
  }
}

この値は一例です。報告者は16Gを設定しています。上限に達するとカーネルがコマンドを落としますが、結果に上限の名前は出ません。設定の詳細はCLAUDE_CODE_TOOL_MEMORY_LIMITの解説にあります。

注意点が1つあります。報告者によると、Claude Codeが設定するcgroupのmemory.maxは効いても、スワップは無制限のままです。上限に届く前にスワップが埋まる可能性があります。報告者は、共有cgroup(app.slice/claude-code-bash)のmemory.swap.maxをSessionStartフックで設定する対策を足しています。この調整は公式の手順ではなく、環境ごとの判断です。

3. PreToolUseフックで広すぎるfindを止める

報告者はPreToolUseフックで、-xdevの無いfind /とfind /mnt配下のパスをブロックしています。フックはツール呼び出しの直前に走り、permissionDecisionに"deny"を返すと呼び出しを止められます。

#!/bin/bash
# block-wide-find.sh — ルートや/mnt配下(find /mnt/c など)を-xdevなしで走査する find を止める例
CMD=$(jq -r '.tool_input.command')
 
if echo "$CMD" | grep -Eq '(^|[;&|]|\s)find\s+(/|/mnt(/[^[:space:]]*)?)(\s|$)' \
   && ! echo "$CMD" | grep -q -- '-xdev'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "find / と find /mnt 配下は -xdev を付けて範囲を絞ってください"
    }
  }'
fi
exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/block-wide-find.sh" }
        ]
      }
    ]
  }
}

これは検知の一例です。find "$HOME/.."のような書き方や、変数経由のパスは正規表現をすり抜けます。完全には防げませんが、モデルがよく書くfind /型の呼び出しには効きます。フックの返し方はフックのリファレンスを参照してください。

パイプの先が閉じないと止まらない

事故の発端が| head付きの検索だった点は、見落としやすい部分です。headは行を受け取ってから先頭を切り出すので、一致が0件ならfindは走り続けます。

終わりの無い走査には、timeoutのような時間制限を併用する手があります。たとえばtimeout 60 find …なら、60秒で打ち切れます。これはシェル側の一般的な手法で、今回のissueの報告には含まれていません。

修正の見通し

issueの時点で、直し方は2系統あります。

  • bfs側: 作者はコメントで、d_type == DT_DIRのときもシンボリックリンクをたどらないようにする修正を入れ、効果が見込めると書いています。ジャンクションをDT_DIRと返すntfs3での扱いが改善に向かう見込みです
  • カーネル側: 作者はntfs3の不具合に見えるとしており、ジャンクションをDT_LNKとして返す直しが入るかは、このissueでは決まっていません

bfsの修正がClaude Codeに同梱されて届く時期は、issueには書かれていません。当面は、上の予防策で備えるのが現実的です。issueがcloseされたかは、リンク先で確認してください。

発生時の切り分けの流れ

「Claude Codeが重い」「メモリが急に減った」というとき、次の順で見ていくと原因を絞れます。

切り分け

findが原因かを確かめる

  1. Qpsにbfs由来のプロセス(バージョン番号の名前)が長時間動いているか?
    答えが別の原因を疑う

    プロセス名はbfsではなく、Claude Codeのバージョン番号で表示されることがあります。引数に-S dfsが見えれば、この系統です。

    答えがはい
  2. Q走査先に`ntfs3`のマウントが含まれているか?
    答えがシンボリックリンクの循環など別の可能性

    mount | grep ntfs3で確かめます。含まれるなら、ジャンクションの影響を疑います。

    答えがはい

マウントを外すか、-xdevと上限を付けて再実行

止めたあとは、範囲を絞ったfindと、メモリ上限・フックの組み合わせで再発を防ぎます。

まとめ

今回の問題は、Claude Codeのfind(bfs)が、ntfs3の返すジャンクションの種類を信用して、自己参照のループに入ることです。GNU findは全モードで循環を検出するとみられ、同じ環境でも差が出ます。

Linuxにntfs3でWindowsドライブをマウントしているなら、ルートからのfindは避け、メモリ上限とスワップ上限を付けておくと被害を小さくできます。Windowsドライブを使わない環境では、この問題を気にする必要はありません。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. github.com/anthropics/claude-code/issues/104497
  2. code.claude.com/docs/en/tools-reference
  3. code.claude.com/docs/en/env-vars
  4. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn