I have been shipping wallpaper and meditation apps as an indie developer since 2014, and as that business grew I started feeding App Store Connect CSVs and access logs into Claude Code for analysis. One day I asked Claude Code to read a 100,000-line access log and extract every line containing a particular checkout keyword. The result came back suspiciously short. After a few minutes of confusion I realized what had happened: the Read tool had quietly stopped at the first 2000 lines. No error. No warning. Claude was reasoning over a fraction of the file without knowing the rest existed.
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.
My grandfathers were both temple carpenters in Japan, and one principle I inherited from watching them work is "understand the tool's grain before you use it." Once you accept that Read quietly cuts and never warns, you stop blaming the wrong things when results look off.
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.csvHow 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.txtIf 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 separation-of-duties idea runs through Claude Code Bash Tool Execution Errors and How to Fix Them, and once you start dividing work by tool the failure modes shrink dramatically.
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.csvFor 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.jsonClaude 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.
If you are juggling many of these concerns inside one long session, How to Keep Claude Code's Context Healthy Across Long Sessions covers the broader budgeting question and pairs well with this article.
My Pre-Read Checklist
This is the routine I run before handing any sizeable file to Claude Code.
I start with wc -l and ls -lh to see how big the thing actually is. If the line count is over 2000, I stop reaching for Read directly and pre-filter with grep or awk. Anything that looks like aggregation gets done in bash, with only the summary handed to Claude. Since adopting this flow, silent-truncation incidents have basically disappeared.
Ten years of indie development has taught me that 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 the rest 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.