Why Claude Code Forces Itself to Read Files Before Editing Them
Claude Code edits thousands of files for developers every day. Behind each edit is a system designed to prevent data corruption, save tokens, and handle edge cases you'd never think about — like curly quotes, infinite device files, and race conditions.
I reverse-engineered the source code (v2.1.88) and found some surprisingly clever engineering decisions. Here are the most interesting ones.
The Read-Before-Edit Rule
Claude Code physically cannot edit a file it hasn't read first. This isn't a suggestion — it's enforced in code.
When the AI model calls FileEditTool, the first thing the tool checks is whether the file exists in a readFileState cache. If there's no record of a previous Read, the edit is rejected with an error.
Why? Because LLMs hallucinate.
The model might "remember" a file containing const x = 1 on line 42, but it could be wrong — maybe that line was changed by another process, or the model is confusing it with a similar file. If the edit proceeds based on hallucinated memory, it could corrupt the file.
By forcing read-before-edit, every modification is based on actual file contents, not the model's potentially faulty recall.
The Curly Quote Problem
Here's a problem I never would have anticipated: LLMs sometimes generate curly quotes (" ") instead of straight quotes (" ").
When the model wants to replace const x = "hello", it might send the edit request with const x = "hello" (curly quotes). Exact string matching would fail — the old text doesn't match anything in the file.
Claude Code handles this with quote normalization during matching:
Step 1: Try exact match → "hello" vs "hello" → No match
Step 2: Normalize quotes → "hello" vs "hello" → Match found!
Step 3: Replace using the normalized version
This is the kind of edge case that only surfaces after millions of real-world edit operations.
18% of File Reads Are Duplicates
During a typical editing session, the model reads the same file multiple times — once to understand the code, once before editing to confirm contents, and sometimes again after editing to verify.
Claude Code's FileReadTool tracks the last-read state of every file:
Read request for /src/app.ts, lines 1-100
├─ Check readFileState cache
├─ Same file, same range? → Check modification time
├─ mtime unchanged? → Return "file_unchanged" marker
└─ Model receives: "Same as what you read before"
In practice, roughly 18% of Read calls hit this cache. Each cache hit skips file I/O entirely and saves the tokens that would have been spent transmitting the file contents again. For large files, this is significant — a 500-line file at ~4 tokens per line is 2,000 tokens saved per cached read.
Image Token Budgeting
FileReadTool isn't just for text. It handles images, PDFs, and Jupyter notebooks too. For images, there's a token budget system:
Every image sent to the model consumes tokens based on its dimensions. A 4K screenshot could eat thousands of tokens. So Claude Code automatically compresses images to fit within a budget:
Original: 3840×2160 → ~8,000 tokens → Over budget
Compressed: 1920×1080 → ~2,000 tokens → Within budget
The scaling uses sqrt(budget / currentTokens) to maintain aspect ratio while hitting the target token count. The output format is JPEG at quality 80 — a good balance between size and readability.
Device File Protection
Your filesystem contains things that look like files but aren't:
-
/dev/zero— infinite stream of zero bytes -
/dev/random— infinite random data -
/dev/stdin— blocks forever waiting for input -
/proc/PID/fd/0— another process's stdin
If the model naively tries to "read" these, the operation would hang indefinitely or produce infinite output. Claude Code maintains a blocklist of known device files and pseudo-files, rejecting read attempts before they reach the filesystem.
TOCTOU Protection in File Edits
FileEditTool faces a classic race condition: Time-of-Check-to-Time-of-Use (TOCTOU).
The scenario: the tool checks the file contents, confirms the old text exists, then another process modifies the file, and the tool writes its replacement — overwriting the other process's changes.
Claude Code's solution is blunt but effective: the entire read-modify-write cycle happens in a synchronous code block with no async operations in between. There's no gap where another process can slip in a modification. The check and the write are atomic.
Additionally, the tool double-checks file contents immediately before writing, catching any changes that happened between the initial Read and the Edit call.
Line Ending Preservation
A subtle difference between FileEditTool and FileWriteTool:
- FileEditTool preserves the original file's line endings (LF or CRLF)
- FileWriteTool always uses LF
Why the difference? Edit replaces a portion of an existing file, so it should respect the file's conventions. Write creates complete new content, and model-generated text defaults to LF — forcing CRLF conversion could introduce problems in files that should be LF.
Six Output Modes for One Read Tool
FileReadTool automatically detects the file type and returns the appropriate format:
| File Type | Output Mode | What You Get |
|---|---|---|
.ts, .js, .md
|
text | Line-numbered source code |
.png, .jpg
|
image | Compressed Base64 within token budget |
.ipynb |
notebook | Structured cell contents with outputs |
.pdf (small) |
Full Base64 content | |
.pdf (large) |
parts | Per-page image extraction |
| (repeated read) | file_unchanged | Cache hit — no content transmitted |
This means the model can "read" a screenshot, a Jupyter notebook, or a PDF with the same tool interface. The complexity is hidden behind a single Read tool that figures out what to do based on the file extension.
What's Next
These file operations are just one layer of Claude Code's 40+ tool system. The architecture also includes 23 security checks for shell commands, lazy tool loading, parallel execution orchestration, and multi-agent tool restrictions.
I've documented the complete architecture in "Claude Code from the Inside Out" — 12 chapters covering everything from the core agent loop to context management and multi-agent coordination. Based on v2.1.88 source analysis.
These patterns come from my deep-dive into Claude Code's actual source code (v2.1.88). I wrote 12 chapters covering the complete architecture — from the core loop to multi-agent coordination.
📖 Read Chapter 1 free — "What Is an AI Agent? From ChatBot to Claude Code"
If you like it, the full book is available with 50%% off for early readers:
📘 Claude Code from the Inside Out (English) — use code LAUNCH50 for $4.99
📕 深入浅出 Claude Code (中文) — use code LAUNCH50CN for $4.99
More in this series:
Top comments (0)