DEV Community

Charlie Li
Charlie Li

Posted on

Why Claude Code Forces Itself to Read Files Before Editing Them

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) pdf 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)