CLAUDE LABJP
MCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attributionMCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attribution
Articles/Claude Code
Claude Code/2026-05-10Intermediate

When Claude Code's Read Tool Silently Truncates Large Files — offset/limit Patterns and When to Reach for Bash

Claude Code's Read tool stops at 2000 lines without an error. Here are the signs of silent truncation, a preflight script that decides how to read a file, and the sentinel-line trick that proves you reached the end.

claude-code129troubleshooting87read-toolfilesbash4

I asked Claude Code to read a 100,000-line access log and pull out every line containing a particular checkout keyword. The count that came back was an order of magnitude off from what grep -c reported on the same file.

The cause was mundane. The Read tool had quietly stopped at the first 2000 lines. No error. No warning. Claude was reasoning over the head of the file, treating it as the whole thing. Working solo on my own apps, there is nobody to catch that discrepancy for me — without the habit of cross-checking counts, I can spend an afternoon building on a wrong number.

Silent truncation like this is a class of bug that hits anyone working with logs, CSVs, large JSON, or minified bundles in Claude Code. After getting bitten by it a few times I built a mental model for when Read is the right tool, when offset and limit come in, and when the answer is "do not use Read at all, drop into bash." Here is what I have settled on.

Why Read Caps at 2000 Lines

Read returns at most the first 2000 lines by default. The cap exists to protect Claude's context window, and the failure mode is to return a partial file without raising an error. When the information you need lives below line 2000, Claude has no signal that anything is missing — it confidently reasons over the head of the file as if it were the whole thing.

What makes this hard is that truncation does not produce a wrong-looking answer. It produces a plausible one. If a tool claims to have found something that does not exist, you get suspicious. If it says "not found," you tend to believe it. Until you internalize that Read cuts silently and never warns, you will keep attributing the mismatch to something else.

Three Signs You Got Truncated

A few signals tell you truncation is in play.

The first is the response ending exactly at line 2000. If you suspect the file is larger and Claude's last line number is 2000, assume it was cut.

The second is file size above roughly 100 KB. At an average of 50 bytes per line, 2000 lines is about 100 KB. Anything heavier is almost certainly past the cap.

The third is the answer "not found" when you know the data is in there. When Claude reports that a December record does not exist in a file that obviously contains a full year, run wc -l first.

# First sanity check whenever truncation feels possible
wc -l /path/to/large.csv
ls -lh /path/to/large.csv

Stop Eyeballing It — Let a Script Decide

Those three signals are worth knowing, but checking them by hand every time does not survive a busy week. At some point I started routing every sizeable file through a small script before handing it to Claude Code. It does not read the file for me; it just prints a plan.

#!/usr/bin/env bash
# read-plan.sh — decide how to read a file before handing it to Claude Code
# usage: ./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")
 
# Binary check via NUL bytes; the first 4 KB is enough
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=binary (never Read this)"
elif [ "$maxlen" -gt 500 ]; then
  echo "PLAN=BASH_ONLY   reason=longest line is ${maxlen} chars (minified or single-line JSON)"
elif [ "$lines" -gt 2000 ]; then
  echo "PLAN=READ_RANGED reason=${lines} lines (split with offset/limit, or grep first)"
  echo "  offset candidates: $(seq 0 2000 $((lines - 1)) | tr '\n' ' ')"
else
  echo "PLAN=READ_DIRECT reason=${lines} lines (safe to Read as-is)"
fi

Three outcomes, and that is the whole point. READ_DIRECT means hand it over. READ_RANGED means walk it with offset. BASH_ONLY means summarize in bash and give Claude the summary. Not having to re-derive that judgment matters most on the days you are tired.

One practical caveat: wc -l counts newline characters, so a file without a trailing newline reports one line short. That difference only bites near the boundary, around 2000 lines, which is exactly where you want to round toward caution.

How I Use offset and limit

Read accepts two arguments that most people skip past in the docs: offset (the line number to start from) and limit (how many lines to read). Using these well is the difference between "Read keeps cutting things" and "Read does exactly what I want."

Pattern 1: You Know Roughly Where to Look

When you have a sense of the line range — a function definition, a section heading, the timestamp of an error — pass offset and limit directly.

Read(file_path="/path/to/server.log", offset=4500, limit=200)

For logs, run grep -n "ERROR" server.log | head first to surface the line numbers of interest, then point offset at one of them.

Pattern 2: You Cannot See the Target

If you are looking for "every line containing X" or "how many rows match Y", filter in bash before calling Read.

# Narrow the file first, then read what is left
grep -n "checkout" /path/to/access.log > /tmp/checkout-lines.txt
wc -l /tmp/checkout-lines.txt

If the filtered output is under 2000 lines, Read it. If not, narrow further with head -100 or another grep.

Pattern 3: You Want a Whole-File Scan

This is bash territory, not Read territory. Read is built to give Claude context, not to be a data-processing engine. Aggregations and full scans belong in bash, awk, or Python — feed Claude only the summary.

The same instinct shows up in Why Claude Code's Glob and Grep Return Zero Results. Learning to ask whether a search tool found nothing or simply could not see everything is exactly the habit that protects you from silent truncation.

Prove You Reached the End with a Sentinel Line

After splitting a file across several reads, you often want to know whether the last chunk actually landed. Counting line numbers by eye is tedious, and Claude's own "I've read the file" is not evidence of anything.

The trick I use is to append a marker line to a copy of the file.

# Append a sentinel to a temporary copy
cp /path/to/large.csv /tmp/with-sentinel.csv
printf 'EOF_MARKER_%s\n' "$(date +%s)" >> /tmp/with-sentinel.csv
 
# Keep the expected value handy
tail -1 /tmp/with-sentinel.csv

Then ask Claude Code: "when you finish reading, echo back the line starting with EOF_MARKER_." If that line comes back, you reached the bottom. If it does not, either the read was truncated or your offset walk has a gap in it.

It turns "I think it read everything" into "I have proof it did." As a preflight step before handing off an aggregation task, that is a cheap trade. Always append to a copy, never to the original.

CSVs Are Where This Bites Hardest

The App Store Connect reports for my apps run to tens of thousands of rows per month. Letting Claude Code Read one of them directly works fine for the headers and the first few thousand lines, but a query like "what was December's total" can come back wrong because December lives at the bottom of the file, past the cap.

My standard CSV workflow now looks like this:

# 1. Inspect the header
head -1 /tmp/sales-2025.csv
 
# 2. Filter on the column you care about (rows where revenue >= 1000)
awk -F',' '$5 >= 1000' /tmp/sales-2025.csv > /tmp/filtered.csv
wc -l /tmp/filtered.csv
 
# 3. Compute the aggregate in bash, hand only the answer to Claude
awk -F',' '{sum+=$5} END {print sum}' /tmp/sales-2025.csv

For CSVs, asking Claude to reason over raw rows is almost always worse than asking Claude to interpret a small numeric summary.

Logs and the "Yesterday's Errors" Trap

A common version of this bug is asking Claude to investigate "yesterday's errors" while Read quietly returns a window of yesterday morning's traffic and stops. The pattern I keep on hand for log triage is:

# Pull error lines plus surrounding context, then take only the recent ones
grep -nB 2 -A 5 "ERROR\|Exception" /var/log/app.log | tail -200

-B and -A give you context, tail keeps it recent. The result fits comfortably in the cap. Real-time tailing (tail -f) does not work well as a Claude input — take a fixed snapshot instead, hand it over, and ask for analysis.

JSON Files Need Their Own Approach

Large JSON files are a separate trap. A 5 MB JSON file might be a single line, in which case Read will not "truncate at line 2000" — it will pull the whole thing and burn enormous context. A formatted JSON file, on the other hand, can run to 50,000 lines and get cut.

For JSON I almost never let Claude Read directly. The pattern is to extract just the keys or just the relevant subtree with jq, then feed Claude the shape.

# What is the top-level shape?
jq 'keys' /tmp/big-payload.json
 
# How many entries are in the items array?
jq '.items | length' /tmp/big-payload.json
 
# Pull a representative sample of three records
jq '.items[0:3]' /tmp/big-payload.json

Claude can do excellent reasoning over a three-record sample plus a count and a key list. It struggles when handed 50,000 lines of nested objects.

Do Not Read Binary or Minified Files

Truncation is one issue. Wasting tokens is another. Read assumes text. Pointing it at minified JS bundles (megabytes with almost no line breaks), images, PDFs, or SQLite files burns context and degrades Claude's reasoning.

My personal rule: if a single line is over 500 characters, or the file is larger than 1 MB, do not Read it — summarize from bash instead. For a minified bundle, wc -c plus inspection of package.json or the source map tells you more than dumping the file.

The broader idea — designing around tools that fail quietly rather than loudly — is covered in Running Claude Code Without Breakage: A Failure-First Production Workflow. Truncation is one of the least visible failures in that category.

A Bigger Context Window Does Not Lift the Read Cap

The July 2026 Claude Code update made Opus 5 the default Opus model, with a 1M-token context window. The obvious thought follows: if the context is that wide, can I just hand over the big file?

Not quite. Context size and tool output limits live on different layers. The context window is how much the model can hold at once; the 2000-line cap is a property of the Read tool itself. Moving to a larger model does not change what the tool decided to return.

Where the wider context genuinely helps is downstream. Walking a file with offset across ten chunks and keeping all ten in play used to run into the "it forgot the early chunks" problem. That constraint has eased noticeably.

So the practical stance is unchanged in one direction and improved in the other: still split large files or filter them in bash, but expect to carry far more of the split results through a single session. Default models and pricing shift quickly, so verify against primary sources before you build a workflow on top of them.

My Pre-Read Checklist

This is the routine I run before handing any sizeable file to Claude Code.

Everything goes through read-plan.sh first. READ_DIRECT gets handed over as-is; READ_RANGED gets pre-filtered with grep or awk. Anything that smells like aggregation is finished in bash, with only the summary passed to Claude. When reaching the end of the file actually determines the answer, I append a sentinel line before I start. Since adopting this flow, silent-truncation incidents have basically disappeared.

Learning a tool's limits before relying on it is the same skill as preventing bugs. Bringing Claude Code into a serious workflow is no different — get the Read behavior into your fingers through small experiments first, and everything downstream gets easier.

What to Try Next

Grab a real log or CSV with at least 100,000 lines. Ask Claude Code to call Read(file_path=..., offset=0, limit=100) and then run wc -l in bash. Once Claude can articulate "the file has N lines and the first 100 contain X," using offset and limit deliberately becomes second nature.

Thanks for reading. I hope this saves someone the afternoon I lost to a silently truncated log file.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-05-13
Claude Code Bash Tool Hangs on `npm run dev` and `docker run` — How to Handle Long-Running Processes
Claude Code's Bash tool freezes when running npm run dev, docker run, or other long-running processes. Learn background execution patterns, timeout strategies, and process management best practices.
Claude Code2026-04-08
Fix Claude Code Bash Tool Execution Errors — Timeouts, Permission Denied, and Truncated Output
Learn how to troubleshoot and fix Claude Code Bash tool errors including timeouts, Permission Denied, ENOENT, truncated output, and environment variable issues with step-by-step solutions.
Claude Code2026-06-18
When a Broken settings.json Stops Claude Code From Starting — Safe Mode and How to Split Your Config
How to find which config layer is broken when a settings.json syntax error stops Claude Code from starting, recover in minutes, and structure your settings so an automated pipeline can't quietly break itself.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →