Chapter 6: File Editing and Patch Application
Why Editing Is Harder Than Writing
Generating new code is easy compared with safely modifying existing code. An agent edit must preserve surrounding context, avoid clobbering user changes, handle encodings, respect permissions, and leave a diff a human can review.
There are two dominant editing styles:
- Patch-based editing: describe a diff and apply it.
- String-replacement editing: replace an exact old string with a new string.
Codex mainly uses patch application. Claw exposes file write and string replacement style editing.
Editing In A Safe Agent
Before any edit, the runtime should answer:
def prepare_edit(path, workspace, permission_policy):
real_path = canonicalize(path)
if not real_path.is_relative_to(workspace.root):
raise PermissionError("edit escapes workspace")
if is_binary(real_path):
raise ValueError("refuse text edit on binary file")
permission_policy.require_write_access(real_path)
original = read_text(real_path)
return real_path, original
After the edit, the runtime should preserve enough information for review:
def finish_edit(path, before, after):
if before == after:
return {"changed": False, "message": "No changes applied"}
return {
"changed": True,
"path": str(path),
"diff_preview": unified_diff(before, after),
}
Codex: Patch-Based Editing
Codex has a custom patch tool rather than relying on external patch or
git apply. The model emits a patch-like operation, the runtime parses it,
computes the affected files, checks permissions, and applies the change through
the tool orchestration path.
Patch Editing Shape
def apply_patch_document(patch_text, workspace):
operations = parse_patch(patch_text)
for op in operations:
path = resolve_workspace_path(op.path, workspace)
before = read_text(path) if path.exists() else ""
if op.kind == "add":
after = op.new_text
elif op.kind == "delete":
after = None
elif op.kind == "update":
after = apply_hunks(before, op.hunks)
elif op.kind == "move":
after = apply_hunks(before, op.hunks)
schedule_move(op.old_path, op.new_path)
validate_result(op, before, after)
commit_all_file_changes(operations)
The important runtime behavior is not only applying text. It is validating that the patch refers to the expected old content and that each touched path is allowed under the current policy.
Why Patches Work Well For Agents
Patch-based editing has useful properties:
- Multi-file changes fit in one tool call.
- Review output is naturally diff-shaped.
- The model can express insertion, deletion, movement, and replacement.
- The runtime can compute write permissions from the patch before applying it.
- Failed context matches prevent accidental edits in the wrong location.
The downside is that patch syntax is another language the model must emit correctly. Codex mitigates this with a strict parser and clear tool grammar.
Claw: String Replacement And File Writes
Claw's file operations include read, write, edit, glob, and grep style tools. The edit operation is conceptually string replacement: find an old string in a file and replace it with a new string. Write operations can create or overwrite files after permission checks.
String Replacement Shape
def edit_file(path, old_string, new_string, replace_all=False):
real_path, before = prepare_edit(path, workspace, permission_policy)
count = before.count(old_string)
if count == 0:
raise ValueError("old_string was not found")
if count > 1 and not replace_all:
raise ValueError("old_string is not unique")
if replace_all:
after = before.replace(old_string, new_string)
else:
after = before.replace(old_string, new_string, 1)
write_text(real_path, after)
return finish_edit(real_path, before, after)
This editing style is simple and predictable. It pushes the model to read the file first, select an exact span, and replace only that span.
Why String Replacement Works Well
String replacement has useful properties:
- One file is changed at a time, so the blast radius is small.
- The old text acts as a guard against stale context.
- Failure is understandable: not found or not unique.
- It is easy to enforce file-size limits and binary detection.
The downside is that larger refactors require multiple tool calls. If the model does not include enough exact context, the replacement can fail.
Whole-File Writes
Whole-file writes are necessary for new files and generated assets, but they are riskier for existing files. A safe runtime should treat overwrite differently from create.
def write_file(path, content, overwrite=False):
real_path = resolve_workspace_path(path)
permission_policy.require_write_access(real_path)
if real_path.exists() and not overwrite:
raise ValueError("file exists; use edit instead of write")
if len(content.encode("utf-8")) > MAX_WRITE_BYTES:
raise ValueError("content too large")
atomic_write_text(real_path, content)
return {"path": str(real_path), "bytes": len(content)}
Claw's file layer includes explicit size checks and binary safeguards. Codex's patch flow similarly computes write targets before applying changes.
Editing And User Changes
An agent should never assume the file it read is still unchanged. Between read and write, the user or another tool may modify the file. A robust edit flow can compare against the expected content:
def guarded_edit(path, expected_before, transform):
current = read_text(path)
if current != expected_before:
raise RuntimeError("file changed since it was read")
after = transform(current)
write_text(path, after)
Real systems vary in how strict they are, but the principle matters: edits should be anchored in observed file content, not only model memory.
Comparison
| Aspect | Codex Patch Tool | Claw File Edit |
|---|---|---|
| Main abstraction | Patch document with hunks | Exact string replacement |
| Multi-file edits | Natural | Requires multiple calls |
| Guard against stale context | Hunk context | old_string match |
| Review shape | Diff-first | Result plus optional diff preview |
| Parser complexity | Higher | Lower |
| Model burden | Must emit valid patch grammar | Must choose unique old string |
| Permission planning | Compute touched paths from patch | Check target path per call |
Practical Rule For Agents
The best editing strategy is usually:
def choose_edit_strategy(change):
if change.creates_new_file:
return "write_file"
if change.touches_many_files or needs_reordering(change):
return "apply_patch"
if change.is_small_local_replacement:
return "string_replace"
return "read_more_context_first"
Codex optimizes for patch fluency. Claw optimizes for guarded local edits and structured file operations.
Source Anchors
For Codex, useful filenames are apply_patch.rs and the patch parser files. For
Claw, useful filenames are file_ops.rs, permission_enforcer.rs, and
tools/lib.rs.