10万行を超えるアクセスログを Read ツールで開かせて、「特定のキーワードを含む行を全部拾って」と頼んだときのことです。返ってきた件数が、手元で grep -c を叩いた結果と一桁違っていました。
原因は単純でした。Read がファイルの先頭2000行で静かに止まっていただけです。エラーは出ません。Claude 自身も切り捨てに気づかないまま、読めた範囲を全体だと思って推論を続けていました。個人開発ではこの手の食い違いを指摘してくれる相手がいないので、数字を突き合わせる癖がないと数時間そのまま進んでしまいます。
このような切り捨てに気づかず、誤った集計や差分検出をしてしまう事故は、ログ・CSV・大きな JSON・minify されたバンドル等を扱う場面で誰もが遭遇します。今回は、私自身がこの落とし穴で何度かハマりながら整理してきた Read ツールの挙動と、offset / limit の使い分け、そして bash で代替すべき判断基準を共有します。
なぜ Read ツールは2000行で切り捨てるのか
Read ツールはデフォルトで先頭から最大2000行を返します。これは Claude のコンテキストウィンドウを保護するための仕様で、エラーを返さず黙って末尾を切るのが特徴です。ファイル末尾に必要な情報があるとき、Claude にはその「不在」が見えないため、嘘ではなく単に部分情報で推論を続けてしまいます。
厄介なのは、この挙動が「間違った答え」ではなく「正しく見える答え」を返すことです。存在しないものを見つけたと言われれば疑えますが、見つからなかったと言われると、つい素直に受け取ってしまいます。Read に関しては「黙って切る」という性質を先に押さえておかないと、結果のズレを別の原因に押し付けたまま長い時間を溶かします。
切り捨てを見抜く3つのサイン
切り捨てが起きているかどうかは、以下のいずれかで判別できます。
第一に、返ってきた行数がきっかり2000行であること。3000行以上ありそうな実ファイルに対して、応答末尾の行番号が 2000 で終わっている場合はほぼ確実に切れています。
第二に、ファイルサイズが100KBを超えている場合。1行あたり50バイトと仮定すると2000行で約100KB相当となり、これを超えるサイズのテキストファイルは2000行を超えている可能性が高いです。
第三に、末尾に必要な情報があるのに見つからない現象。「2025年12月のレコードを探して」と頼んだのに「該当なし」と返ってきたときは、まずファイル全体の行数を wc -l で確認してください。
# 切り捨てが起きていそうなときの最初の確認
wc -l /path/to/large.csv
ls -lh /path/to/large.csv見抜くのをやめて、機械に判定させる
3つのサインは覚えておく価値がありますが、毎回人間が判断するのは長続きしません。私はある時期から、大きめのファイルを Claude Code に渡す前に必ず通す小さなスクリプトを置くようにしました。読み方の「方針」だけを出力させるものです。
#!/usr/bin/env bash
# read-plan.sh — Claude Code に渡す前に、そのファイルの読み方を判定する
# 使い方: ./read-plan.sh path/to/file
set -euo pipefail
f="${1:?usage: read-plan.sh <file>}"
[ -f "$f" ] || { echo "NOT_FOUND $f"; exit 1; }
bytes=$(wc -c < "$f" | tr -d ' ')
lines=$(wc -l < "$f" | tr -d ' ')
maxlen=$(awk '{ if (length($0) > m) m = length($0) } END { print m+0 }' "$f")
# NUL バイトの有無でバイナリを判定(先頭4KBだけ見れば十分)
head_bytes=$(head -c 4096 "$f" | wc -c | tr -d ' ')
strip_bytes=$(head -c 4096 "$f" | LC_ALL=C tr -d '\000' | wc -c | tr -d ' ')
kind=text
[ "$head_bytes" != "$strip_bytes" ] && kind=binary
echo "file=$f bytes=$bytes lines=$lines max_line_len=$maxlen kind=$kind"
if [ "$kind" = binary ]; then
echo "PLAN=BASH_ONLY reason=バイナリ(Read で開かない)"
elif [ "$maxlen" -gt 500 ]; then
echo "PLAN=BASH_ONLY reason=最長行が ${maxlen} 文字(minify または1行JSONの疑い)"
elif [ "$lines" -gt 2000 ]; then
echo "PLAN=READ_RANGED reason=${lines} 行(offset/limit で分割するか、grep で先に絞る)"
echo " offset 候補: $(seq 0 2000 $((lines - 1)) | tr '\n' ' ')"
else
echo "PLAN=READ_DIRECT reason=${lines} 行(そのまま Read してよい)"
fi出力は3種類だけです。READ_DIRECT ならそのまま渡す。READ_RANGED なら offset を進めながら分割して読む。BASH_ONLY なら Read を使わず bash で要約してから渡す。判断を毎回やり直さずに済むので、疲れている日ほど効きます。
ひとつ実務上の注意があります。wc -l は改行の数を数えるため、末尾に改行がないファイルでは1行分少なく出ます。境界ぎりぎり(2000行前後)のファイルでこの差が効いてくるので、判定はいつも安全側に倒すつもりで見てください。
offset と limit の使い分けパターン
Read ツールには offset(読み始める行番号)と limit(読む行数)のパラメータがあります。これは「ファイル全体を読まなくても目的の範囲だけ取れる」ためのもので、使い分けを覚えると切り捨て事故が激減します。
パターン1: 目的の行範囲が分かっているとき
特定のセクションや関数定義、エラー発生時刻周辺のログ等、行番号がおおよそ見えている場合は offset と limit を直接指定します。
Read(file_path="/path/to/server.log", offset=4500, limit=200)
ログファイルなら grep -n "ERROR" server.log | head で該当行を先に確認しておけば、offset をピンポイントで指定できます。
パターン2: 目的の場所が見えないとき
「キーワードを含む行を全部抽出したい」「件数を数えたい」場合は、Read を呼ぶ前に bash で絞り込みをかけるほうが速くて確実です。
# キーワードを含む行のみ抽出してから Read で詳細確認
grep -n "checkout" /path/to/access.log > /tmp/checkout-lines.txt
wc -l /tmp/checkout-lines.txt絞り込んだ結果が2000行以下なら、その出力ファイルを Read で読めばよいですし、まだ大きければ head -100 等で更に絞ります。
パターン3: ファイル全体をスキャンしたいとき
これは原則 Read ではなく bash の出番です。Read は「Claude が文脈として読む」ためのツールであって、データ処理エンジンではありません。集計や全件スキャンは bash・awk・python に任せ、結果サマリーだけ Read で取り込むのが本来の使い方です。
同じ考え方は Claude Code の Glob と Grep が0件を返すときの原因切り分け とも地続きです。検索系ツールが0件を返したとき、それが「本当に無い」のか「見えていないだけ」なのかを区別する習慣は、そのまま Read の切り捨て対策になります。
番兵行で「全部読めたか」を確かめる
分割して読ませたあと、「本当に最後まで届いたのか」を確かめたい場面があります。行番号を目視で追うのは面倒ですし、Claude 自身の「読めました」という報告は根拠になりません。
私が使っているのは、末尾に目印の1行を足しておく方法です。
# 一時コピーの末尾に番兵行を置く
cp /path/to/large.csv /tmp/with-sentinel.csv
printf 'EOF_MARKER_%s\n' "$(date +%s)" >> /tmp/with-sentinel.csv
# 期待値を控えておく
tail -1 /tmp/with-sentinel.csvそのうえで Claude Code に「読み終えたら、末尾にある EOF_MARKER_ で始まる行をそのまま書き出してください」と頼みます。その行が返ってくれば末尾まで届いています。返ってこなければ、途中で切れているか、offset の進め方に穴があるかのどちらかです。
「読めた気がする」を「読めた証拠がある」に変える、それだけの工夫です。集計を任せる前の一手間としては安いほうだと私は思っています。元ファイルには追記せず、必ずコピーに対して行うのが前提です。
CSV を扱うときの落とし穴
私が運営しているアプリの App Store Connect レポートは、月次で数万行の CSV になります。これを Read で素直に開かせると、ヘッダー行と先頭数千行は読めても、12月や3月のデータが末尾にある場合に切られて見つからない、という現象が起きます。
CSV を扱うときの私の手順は次の通りです。
# 1. ヘッダーだけ確認
head -1 /tmp/sales-2025.csv
# 2. 必要な列の値を絞り込み(例: 売上が1000円以上の行)
awk -F',' '$5 >= 1000' /tmp/sales-2025.csv > /tmp/filtered.csv
wc -l /tmp/filtered.csv
# 3. 集計だけ取り出す
awk -F',' '{sum+=$5} END {print sum}' /tmp/sales-2025.csvCSV の場合、生データを Claude に渡すよりも、bash で集計してから「合計はいくらだった、上位5件は何か」というサマリーを Claude に判断してもらうほうが文脈効率も高く、切り捨ても起きません。
ログファイルでよくある事故と回避策
「昨日のエラーを調べて」と頼んだのに Read が古い時間帯で止まっていた、という事故は典型的です。私は次のヘルパーパターンを使うようにしています。
# エラー行とその前後5行をまとめて取得
grep -nB 2 -A 5 "ERROR\|Exception" /var/log/app.log | tail -200-B(前)と -A(後)でコンテキストを取り、tail で最近のものだけに絞ります。これで200行程度に収まるので、Claude に直接渡しても切れません。tail -f のリアルタイム監視は Claude には向かないので、一定時間スナップショットを取って Read させるほうが現実的です。
JSON ファイルは別の罠がある
大きな JSON は CSV やログとは違った形で問題を起こします。5MB の JSON が1行で書かれている場合は「2000行で切られる」のではなく、巨大な1行をそのまま取り込んでコンテキストが急激に減ります。一方で整形済みの JSON だと5万行に及ぶことがあり、こちらは普通に切られます。
私が JSON を扱うときは Read ではなく jq で構造だけ取り出し、Claude には「形」と「サンプル」だけ見せるようにしています。
# トップレベルのキーを確認
jq 'keys' /tmp/big-payload.json
# items 配列の件数
jq '.items | length' /tmp/big-payload.json
# 代表的な3件をサンプリング
jq '.items[0:3]' /tmp/big-payload.json3件のサンプルと件数とキー一覧があれば、Claude は十分に推論を進められます。整形済み5万行を丸ごと渡してもむしろ精度が落ちます。
バイナリ・minify されたファイルは Read しない
これは切り捨て以前の話ですが、Read はテキスト前提のツールです。minify された JS バンドル(数MBで改行が極端に少ない)、画像、PDF、SQLite ファイルを Read で開くと、行が長すぎて token を浪費するだけでなく、Claude の応答品質も落ちます。
私の運用ルールはシンプルで、1行が500文字を超えるか、ファイルが1MB を超えたら Read ではなく bash で要約するです。minify バンドルなら wc -c でサイズだけ見て、構造は package.json や source map から推定するほうが速いです。
「ツールが黙って失敗する前提で仕組みを組む」という発想そのものについては Claude Code で壊れないAIコーディングを回す — 失敗前提の本番運用設計 で扱っています。切り捨ては、その中でもいちばん気づきにくい失敗のひとつです。
コンテキストが大きくなっても、Read の上限は別レイヤー
2026年7月の Claude Code 更新で既定の Opus モデルが Opus 5 になり、1M トークンのコンテキストが扱えるようになりました。ここで一度は考えます。コンテキストがこれだけ広いなら、大きいファイルもそのまま渡せるのではないか、と。
結論から言うと、切り捨ての問題は変わりません。コンテキストウィンドウは「モデルが一度に保持できる量」の話で、Read が一度に返す行数の上限はツール側の仕様です。層が違います。モデルを大きくしても、ツールが2000行で止めたものは2000行のままです。
ただ、広いコンテキストが効く場面はあります。offset を進めながら10ブロック読む、といった分割読み込みの結果を、途中で捨てずに全部抱えたまま推論できる。以前なら「読み進めるうちに前半を忘れる」ことが実際の制約でしたが、そこは確実に楽になりました。
つまり方針としては、大きいファイルは相変わらず分割するか bash で絞る。ただし分割した結果を1本のセッションで扱いきる余地は広がった、という理解が実態に近いと感じています。既定モデルや料金は変わりやすいので、運用に組み込む前に一次情報での確認をおすすめします。
私が普段使っているチェックフロー
最後に、Claude Code に大きなファイルを渡す前に私が踏んでいる確認フローをまとめます。
最初に先ほどの read-plan.sh を通します。READ_DIRECT ならそのまま渡し、READ_RANGED なら grep や awk で必要な部分を抽出してから渡す。集計が必要な場合は bash 側で完結させ、サマリーだけを Claude に渡します。末尾まで読めたかどうかが結論を左右する作業のときは、番兵行を足してから始めます。これだけで切り捨て事故はほぼなくなりました。
ツールの限界を先に把握しておく習慣は、そのままバグの予防につながります。Claude Code を本格的に運用へ組み込むなら、Read の挙動を小さな再現実験で押さえておくのが、回り道のようでいちばん近道です。
次にやってみてほしいこと
手元にある10万行以上のログまたは CSV を1つ用意して、Read(file_path=..., offset=0, limit=100) で先頭だけを取り、続けて bash で wc -l を実行する流れを Claude Code に頼んでみてください。「全体は何行で、先頭100行に何が含まれていたか」を Claude が言語化できれば、それ以降は offset と limit を意識して使い分けるリズムが自然と身につきます。
お読みいただきありがとうございました。同じ場所で詰まっている方の助けになれば嬉しいです。