DEV Community

Zhengxin
Zhengxin

Posted on

Claude Code Tools Deep Dive (7): Write

This is the seventh article in my series on Claude Code tools. The first six covered the interaction primitive trio—AskUserQuestion, EnterPlanMode, and ExitPlanMode—the search duo Grep + Glob, and the perception-and-precision pair Read and Edit. This article examines Edit’s sibling: Write.

Grep + Glob + Read + Edit handle most workflows that follow the sequence “locate, read, then modify precisely.” But Edit cannot do two things: create a file or rewrite one completely. Those jobs belong to Write.

Write appears simple—it writes content to a file—but its design contains a distinctive tension. It is necessary because it is the only tool that can create files, and dangerous because it can overwrite any existing file. The entire Write prompt is designed around managing that tension.

This series begins with a prerequisite article explaining what tools are and how Claude uses them. Like the other articles, this one follows the four-layer framework introduced there.

Write

What it does

Write is Claude Code’s built-in full-file writing tool. Its behavior is straightforward: provide an absolute path and a block of text, and it writes that content to the file. If the file exists, Write replaces it entirely. If it does not exist, Write creates it.

It solves the core problem of how an AI can produce a new file—or perform a complete rewrite—safely and explicitly:

  1. It is the only execution tool that creates files. Edit cannot create them, while Bash can but lacks the same review surface.
  2. It is the most economical path for a complete rewrite. When more than roughly 80% of a file must change, one Write is more efficient than a long sequence of Edit calls.
  3. It requires an overwrite to be grounded in real state. An existing file must be read before it can be written, preventing hallucinated overwrites.
  4. It makes the entire artifact reviewable. The tool call contains the complete text that will be written to disk.

A concrete example

Scenario: The user says, “Add a UserBadge component that displays the user’s avatar, name, and status indicator. Put it in src/components/UserBadge.tsx.”

This is a classic create-a-file-from-scratch task. The project does not contain UserBadge. After exploring the project’s conventions, Claude is ready to create the component.

How Write solves it

Claude calls Write with two parameters:

  • file_path: /Users/xxx/project/src/components/UserBadge.tsx, an absolute path
  • content: the complete component implementation, perhaps 40 lines

What happens at runtime:

  • The runtime checks whether the target’s parent directory exists. If it does not, the call fails.
  • If the file already exists, the runtime checks whether it has been read during the current conversation. If not, the call fails. This is the same harness-tracking mechanism used by Edit.
  • The runtime writes all of content to disk.
  • If the file is new, it creates it; if the file exists, it replaces the entire contents.

The user sees something like this in the tool log:

Write(file_path: src/components/UserBadge.tsx, content: [full 40-line component])
→ File created
Enter fullscreen mode Exit fullscreen mode

One operation produces the complete file without touching anything else.

The bad alternative: using Write for Edit’s job

Return to the previous article’s example. The user wants to rename handleClick to handleSubmit in an existing 600-line file, changing only four occurrences.

If Claude insists on using Write instead of Edit:

  • It reads the entire 600-line file.
  • It performs four replacements mentally.
  • It writes all 600 modified lines back.

Several problems follow:

  1. Severe token waste. All 600 lines travel through Write’s content parameter even though only four locations change.
  2. An uncontrolled blast radius. Write overwrites the whole file. One missing space, changed quote, or omitted line can corrupt unrelated code.
  3. A difficult review. The tool log contains 600 lines of content; the user needs a separate diff to understand the actual change.
  4. Amplified concurrency conflicts. If the user just saved another change in a different editor, Write can overwrite it completely.
  5. Accidental-overwrite risk. Write has no equivalent of Edit’s “old_string must match” safety net. Incorrect content can still be written successfully.

Key insight: Write and Edit are not substitutes. They divide responsibilities. Write handles creation and complete rewrites; Edit handles incremental changes. Mixing them discards the distinctive safety guarantees of both tools.

When to choose Write and when to choose Edit

Scenario Choose Write Choose Edit
Create a file from scratch ✅ The only choice ❌ Cannot create files
More than 80% of a file changes ✅ A full rewrite is more economical ⚠️ old_string becomes long and brittle
Less than 20% of a file changes ⚠️ Wastes tokens and increases risk ✅ Precise replacement
Rename a variable or function ❌ Not recommended ✅ Use replace_all
Fix a typo ❌ A sledgehammer for a tiny task ✅ One replacement
Generate configuration or boilerplate ✅ Write it once ❌ Cannot edit a nonexistent file

A useful rule of thumb: if most of your new_string or new_content would be copied from the old file, use Edit. If most of it is newly written, use Write.

When it is triggered

The official description is deliberately restrained: prefer editing existing files, and do not create new ones unless they are explicitly needed. This is an explicit arbitration rule for the default competition between Write and Edit.

Use Write when:

  • The user explicitly asks for a new file: “add a component” or “generate a configuration.”
  • A new module is required: for example, when splitting existing code into separate files.
  • A file needs a complete rewrite: more than roughly 80% changes and Edit would require a long, brittle old_string.
  • Generating boilerplate: scaffolding, test templates, or migration files.

Do not use Write when:

  • Making a small change to an existing file. Use Edit, whose strength is exact replacement.
  • Creating documentation or a README unless the user asks for it. This is an explicit rule in the tool description.
  • Adding emojis unless the user asks. This is another explicit style constraint.
  • “Verifying” from hallucination. As with the anti-waste principle discussed in the Read article, a successful Write does not need to be followed by a redundant Read.

One especially revealing anti-production rule says: NEVER create documentation files (*.md) or README files unless explicitly requested by the User. The capitalized NEVER reflects painful experience. Early AI coding tools often tried to be helpful by generating README, CHANGELOG, and API documentation files without being asked. Project owners then found their repositories littered with unsolicited Markdown that was awkward to remove. Write’s prompt shuts that anti-pattern down explicitly.

Technical design

1. Naming

Write

The name is as direct as possible: the most basic English verb for putting content down. Together with Read and Edit, it forms a family whose meanings are immediately apparent:

  • Read: perceive the external world.
  • Edit: modify part of existing content.
  • Write: persist the complete content or create a file.

All three verbs operate on files, but their semantic boundaries are clear. Read only consumes; Edit transforms part of something that already exists; Write replaces everything or creates something new. The granularity of the verb encodes the level of danger. Write is the heaviest action of the three, and the name itself signals that weight.

Its fields are equally plain: file_path and content. There is no old_string, new_string, or replace_all because Write does no matching. Its semantics are simply “put this complete content on disk.” The small field set is a form of honesty: Write has no matching safety net and does not pretend otherwise.

2. Tool-level description

Write’s tool-level description is concise. Each constraint addresses a specific aspect of its risk.

Constraint 1: make overwrite behavior transparent

This tool will overwrite the existing file if there is one at the provided path.

The key phrase is will overwrite. There is no softened “be careful” language. Claude is told exactly how destructive the operation is, leaving no room to assume that Write will merge content.

Constraint 2: require Read first

If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.

The words MUST and will fail define a hard barrier, identical to Edit’s prerequisite. The runtime records which files have been read during the current conversation and validates an attempt to overwrite an existing file.

The goals are straightforward:

  • Prevent hallucinated overwrites. Claude may remember a prior version, but the file on disk may have changed.
  • Require a perception commitment. If Claude wants to overwrite a file, it must first demonstrate awareness of what is currently there.
  • Share a trust chain with Edit. Both Read → Edit and Read → Write use the same state machine.

A new file does not need to be read because it has no existing state. The moment a file exists, however, Read becomes mandatory. That is Write’s dual nature expressed at the harness layer.

Constraint 3: prefer Edit over Write

Prefer the Edit tool for modifying existing files—it only sends the diff. Only use this tool to create new files or for complete rewrites.

The words Prefer and Only narrow Write’s legitimate scope to two cases:

  • creating a file
  • rewriting a file completely

This is the authoritative division of labor between Write and Edit. It prevents Claude from overusing Write simply because its semantics are easier.

Constraint 4: do not create documentation proactively

NEVER create documentation files (*.md) or README files unless explicitly requested by the User.

The capitalized NEVER, followed by “unless explicitly requested,” is aimed directly at user experience. It prevents Claude from generating unwanted Markdown files under the guise of being helpful.

This rule is particularly important for Write because Write is the gateway to creating new files. Editing an existing document may be legitimate; introducing a new README, CHANGELOG, or API guide is much more likely to create repository noise.

Constraint 5: do not add emojis by default

Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.

This matches Edit’s style rule. Language models often add emojis to code comments, documentation, and messages, while many professional codebases reject that tone.

Constraint 6: expose the recovery path

This tool will fail if you did not read the file first.

The sentence does more than state a failure. It implies the correction: Read the file, then retry Write. As with Edit’s recovery from uniqueness errors, good prompt design includes the error path.

A combined principle: do not contribute noise proactively

Constraints 3, 4, and 5 combine into a broader value for Write: unless explicitly asked, Claude should not:

  • generate README, CHANGELOG, or documentation files
  • create new files when an existing file can be edited
  • add emojis

These are not schema-level runtime checks. The parameters do not reject .md extensions or emoji characters. They are behavioral training in the description layer, hardcoding the principle that an AI should be cautious about producing artifacts and repository noise.

3. Field-level descriptions

Write’s input schema has only two fields:

  • file_path: the absolute path of the target file.
  • content: the complete content to write.

Their simplicity hides meaningful design choices.

Why file_path must be absolute

The reason is the same as for Read and Edit: remove dependence on the current working directory and make each call self-describing. Across sessions, subagents, and worktrees, an absolute path remains unambiguous.

Why content means the complete content

Compared with Edit’s four fields—file_path, old_string, new_string, and replace_all—Write has no concepts of matching or bulk replacement:

  • Its semantics are “replace the disk contents with this.” There is nothing to match.
  • There is no bulk mode. A Write call is already a complete write.
  • Its failure modes are simpler. It either writes successfully or fails because of permissions, disk state, or an invalid path; there is no intermediate “match not found” state.

That simplicity also means Write lacks Edit’s safeguards: no match verification, no uniqueness check, and no replace_all branch. The blast radius is larger, but the semantics are clearer. The small field set deliberately avoids giving Claude the illusion that Write performs fine-grained adjustment. Pressing Write means replacing everything.

4. Schema validation

At the schema layer, Write has almost no hard constraints. There is no content-length limit, format validation, or content blacklist. Both fields are simply required.

The meaningful constraints live in the runtime state machine:

Check Timing Failure behavior Purpose
Parent directory exists Before writing Reject with an error Prevent typos from creating stray directory trees
Existing file was read in this conversation Before writing Reject with an error Prevent hallucinated overwrites through harness tracking
File does not exist Before writing Create it directly New files have no prior state to read
Permissions, disk, and path are valid During writing Reject with an OS-level error Final system safety net

Why the parent directory is not created automatically

If the target is foo/bar/baz.ts but foo/bar/ does not exist, Write fails rather than creating the directories. This is deliberate:

  • Prevent directory pollution from typos. If Claude writes srcc/component.tsx instead of src/component.tsx, automatic creation would silently pollute the project.
  • Require awareness of project structure. Creating a directory should be an explicit action, such as mkdir -p, not a hidden side effect.
  • Fail loudly. An error is easier to correct than a silent success in the wrong location.

Shared harness state with Read

Read establishes a perception commitment, and both Edit and Write consume it:

  • The same Read state is shared by two execution tools.
  • Edit consumes it to assert, “I know what old_string looks like in this file.”
  • Write consumes it to assert, “I know what I am about to overwrite.”

One Read can therefore support multiple subsequent Edit or Write operations without redundant re-reading.

The division between a minimal schema and a stateful runtime reveals where Write’s true risk lies: not in the parameter format, but in timing and perception. A schema can validate strings, but only the runtime can know whether Claude has perceived the file’s current state.


Division of responsibility among neighboring tools

Write contrasts with the tools discussed in the first six articles:

Dimension Interaction trio Grep + Glob Read Edit Write
Role Collaborative alignment Locate coordinates Perceive the external world Execute precisely Execute in full
Frequency Key moments High-frequency High-frequency High-frequency Medium-frequency
Parameters Structured / empty Pattern File path + pagination Four fields, including old_string Two fields: file_path + content
Semantics Intent signal Location coordinates Perception commitment Incremental replacement Complete overwrite / creation
Safety net User approval head_limit truncation Pagination / mandatory PDF pages Uniqueness / Read / match failure Only Read + existing parent directory
Conservative bias “When uncertain, plan” “Search on demand before reading everything” “When uncertain, read” “When uncertain, Read first” “Prefer Edit; do not create files casually”

Write is Edit’s sibling, not its replacement. Their responsibilities are distinct:

  • Edit performs incremental modification with old_string, new_string, and replace_all, assuming that the file exists and only part should change.
  • Write performs creation or complete rewriting with one block of content, assuming either that the file does not exist or that everything should be replaced.

Using one for the other discards its safety properties. Write used for a small edit wastes tokens, expands the blast radius, and obscures the diff. Edit used for creation cannot work at all.

Together, Grep + Glob → Read → Edit / Write form a chain of five tools sharing harness-tracked state. The mandatory Read prerequisite expresses the central philosophy: any write to disk must be grounded in perception of the current disk state. This is enforced by the runtime, not left to the AI’s self-discipline.


Summary

Write’s elegance does not lie merely in putting content into a file. It lies in how strongly the design relies on description-layer values and a runtime state machine:

  • Naming: one minimal verb in the Read / Edit / Write family. Plain field names and the absence of matching concepts map directly to overwrite semantics.
  • Tool-level description: constraints make overwrite behavior explicit, require Read, prefer Edit, prohibit unsolicited documentation, restrict emojis, and expose recovery. Together, the softer rules encode a principle of not contributing noise proactively.
  • Field design: only file_path and content. The small field set is not limited capability; it deliberately prevents the illusion of fine-grained changes and emphasizes that Write replaces everything.
  • Schema validation: almost empty. Real constraints live in the runtime: the parent directory must exist, existing files must have been read, and Edit and Write share harness-tracked state.

Write is unique because necessity and danger coexist. It is the only tool that can create or completely replace files, so Edit cannot substitute for it. Yet it lacks Edit’s matching safety net and can overwrite hundreds of lines in one call.

The design resolves that tension in three ways: the description narrows Write to creation and complete rewrites; the runtime requires Read before overwriting; and behavioral rules suppress AI anti-patterns such as unsolicited docs, unnecessary files, and emojis. The result is a naturally dangerous capability transformed into an execution primitive that is scope-limited, perception-gated, and resistant to unnecessary noise.

The next article will examine Bash, the most unusual tool in the ecosystem: the only unbounded fallback primitive. The first seven tools all constrain the AI to specific actions; Bash lets it do almost anything. We will see how Claude Code balances that unlimited capability against safety.

Top comments (0)