FileWriteTool:写入文件
它解决的是“整文件写入”问题
FileWriteTool 适合两种典型场景:
- 新建一个文件
- 用一整块新内容覆盖一个现有文件
如果说 FileEditTool 更像外科手术,
那 FileWriteTool 更像“整块材料重新铺设”。
Claude Code 明确把这两件事拆成了不同工具,而不是统统交给 shell。
这就是它工程设计上的一个细节点。
源码里最关键的输入定义
tools/FileWriteTool/FileWriteTool.ts:
const inputSchema = z.strictObject({
file_path: z.string().describe('The absolute path to the file to write'),
content: z.string().describe('The content to write to the file'),
})
它的输入非常干净:
- 写到哪里
- 写什么内容
这说明 Write 的职责就是完整写入,而不是做复杂的局部替换逻辑。
它的输出比你想的更丰富
outputSchema 里不仅返回最终内容,还返回:
type: z.enum(['create', 'update'])
structuredPatch: z.array(hunkSchema())
originalFile: z.string().nullable()
gitDiff: gitDiffSchema().optional()
也就是说,Write 并不是“静默覆盖然后就没了”。
它依然会保留:
- 这是创建还是更新
- 原文件是什么
- 差异补丁是什么
- Git diff 视角下变化如何
这很关键,因为它让整文件写入依然处于 Claude Code 的可审计链路里。
一张图看写入链路
它也会检查文件是否过期
和 Edit 一样,Write 也不是无脑覆盖。
源码里有一段很重要的校验逻辑:
if (!readTimestamp || readTimestamp.isPartialView) {
return {
result: false,
message: 'File has not been read yet. Read it first before writing to it.',
}
}
这说明对于已存在文件,Claude Code 很强调:
- 先读
- 再写
这能防止基于过期内容覆盖别人刚改过的文件。
它为什么还要接入 diff 和 Git
源码里可以看到:
import { countLinesChanged, getPatchForDisplay } from '../../utils/diff.js'
import { fetchSingleFileGitDiff } from '../../utils/gitDiff.js'
这说明即使是整文件写入,Claude Code 也不把它当作“黑盒操作”,而是会继续:
- 统计改动行数
- 生成可展示 patch
- 关联 Git 视角的变化
这样前端和后续模型都能更清楚地理解这次写入发生了什么。
它和 FileEditTool 的分工
这个区别值得反复强调:
Edit:局部修改已有内容Write:创建文件或整文件重写
如果模型只是想改一个函数,Write 往往会显得过重;
但如果是生成新配置文件、新组件骨架、新文档,Write 就非常合适。
一次典型使用路径
最容易误解它的地方
误解一:Write 只是“更方便的 echo > file”
不是。
它接入了权限、文件时序、diff 和 Git 视图。
误解二:Write 比 Edit 更强,所以更应该优先用
也不对。
Claude Code 更强调职责清晰,而不是“越万能越好”。
误解三:Write 只适合新文件
不完全对。
它也能更新已有文件,但更适合整块重写,而不是局部小修。
小结
FileWriteTool 真正值得学的地方是:
Claude Code 连“整文件写入”都没有降级成简单 shell 重定向,而是仍然纳入了受控、可审计、可 diff 的工具系统。
这也是 Claude Code 整体设计非常“工程化”的地方。