中文 Books

Chapter 11: Patches as a First-Class Editing Protocol

Reading Contract: Read this chapter to separate file mutation from shell execution. Track five owners: the patch grammar, the verified action, the approval decision, the executor filesystem, and the turn diff evidence. Afterward, you should be able to answer why a patch failure can still carry useful evidence.

Patch protocol workbench showing patch grammar, verified action, safety gate, executor filesystem, turn diff, and failure feedback as one governed edit lane
Patch handling is not a shell shortcut. It is a governed edit lane: parse the grammar, verify the action, decide safety, apply through the owning filesystem, and preserve evidence.

Source boundary: source-level claims in this chapter refer to the pinned Codex snapshot at commit 569ff6a1c400bd514ff79f5f1050a684dc3afde3. File names, function names, enums, request shapes, and tests are verified source where linked. The short code blocks below are trimmed excerpts or shape-level examples; follow the pinned anchors for complete definitions. Design claims such as “patch is a protocol” are surrounding contract inference from the visible handler, parser, runtime, safety, and diff-tracking code. This chapter does not claim anything about private OpenAI service internals.

Chapter 10 showed how shell execution becomes a supervised process. Patch application looks adjacent because users often type edits in terminal-shaped syntax, but Codex gives it a different owner. The runtime does not have to treat a patch as arbitrary bytes sent to a shell. It can parse a small edit language, verify that the edit matches the current filesystem, compute the paths that would be written, ask for approval when needed, write through the turn’s executor filesystem, and then record the exact delta that actually committed.

That distinction is practical. A direct write says, “make this file contain this content.” A patch says, “add, delete, update, or move these path-scoped hunks against this old content.” The second form is narrow enough to review before mutation and rich enough to explain after failure.

1. The Tool Surface Makes Mutation Explicit

The user-facing apply_patch tool enters Codex as a custom freeform tool rather than a normal shell command. The handler starts as a small ApplyPatchHandler, and its ToolHandler implementation names the tool, exposes the freeform spec, declares matching payloads as function calls, marks the operation as mutating, creates a streaming diff consumer, and supplies pre-tool and post-tool hook payloads for the patch body.

The important point is not the UI name. The handler owns a mutation-specific lifecycle. It can feed pre-tool and post-tool hook payloads with a command-shaped patch body, stream patch updates as hunks are produced, and refuse unsupported payloads before any filesystem write begins.

pub struct ApplyPatchHandler;

fn tool_name(&self) -> ToolName { ... }
async fn is_mutating(&self, ...) -> bool { true }

The handler later re-parses and verifies the patch inside handle. That re-parse is not redundant ceremony. It lets Codex derive concrete path permissions and a patch summary from the same body that will eventually mutate the workspace.

2. Grammar Is Small; Verification Is the Contract

Patch grammar flowing through a parser into an action ledger with path pins and filesystem verification
The grammar is deliberately small. The verified action is richer: paths are resolved, update chunks become diffs, deletes read old content, and moves include both source and destination.

The parser file starts with the visible grammar contract: a patch begins, contains one or more add/delete/update hunks, and ends. The parser module also states that this parser does not by itself check whether the patch can be applied to the filesystem. That boundary matters.

At the syntax layer, the Hunk enum keeps the core shape small. Trimmed to variants, it looks like this:

pub enum Hunk {
    AddFile { ... },
    DeleteFile { ... },
    UpdateFile { ... },
}

Parsing turns text into hunks. Verification turns hunks into an action. maybe_parse_apply_patch_verified resolves the effective working directory, resolves hunk paths against it, reads deleted files, derives update diffs from the current file contents, and records move destinations. If any update hunk cannot find the expected old lines, verification returns a correctness error instead of inventing a best-effort edit.

Shape-level, the transition looks like this:

patch body
  -> hunks: add | delete | update | move
  -> verified action:
       cwd
       absolute paths
       proposed file changes
       raw patch body

Here, “verified” should be read narrowly. It means verified against the visible filesystem state available through the executor filesystem. It does not mean the runtime has proved the edit is semantically correct for the project. That remains a review problem.

2.1 Why Deletes and Updates Need the Filesystem

An add hunk can be represented mostly from the patch body. A delete hunk needs the old content to create evidence for what was removed. An update hunk needs the current file content so Codex can derive a concrete unified diff and the resulting new content. The verification function therefore accepts an ExecutorFileSystem, not just a string parser.

That prevents a common mistake in patch systems: treating parse success as apply success. A patch can be syntactically valid while still failing because the expected old lines are gone, the target file cannot be read, or the destination path is outside the policy boundary.

3. Shell Compatibility Is Intercepted, Not Trusted

Shell heredoc and workdir patch forms passing through a parser gate into patch action, while unrelated shell commands are rejected from the patch lane
Recognized shell forms are compatibility inputs. Once detected, they are governed as patch actions, not as ordinary shell text.

Models and users often express patches as heredocs. Codex recognizes a narrow set of forms rather than treating all shell text as patch text. maybe_parse_apply_patch handles direct invocation such as apply_patch <patch> and shell-script forms that can be extracted. The comments for extract_apply_patch_from_bash describe two supported top-level patterns: an apply_patch heredoc, and a cd <path> && apply_patch heredoc.

The enum makes the boundary explicit:

Body(ApplyPatchArgs)
ShellParseError(...)
NotApplyPatch

The verified path adds one more guard: a raw patch body without an explicit apply_patch invocation is reported as an implicit invocation error. That prevents a patch-shaped string from being silently applied just because it appears in a command position.

When shell interception succeeds inside the main handler, intercept_apply_patch records a model warning that the patch was requested through another tool and tells the model to use apply_patch directly. Then it continues through the same permission, event, runtime, and diff path as a normal patch tool call.

That is the compatibility rule worth copying: tolerate nearby syntax when it clearly maps to the protocol, but immediately normalize it into the governed protocol.

4. Safety Runs Before the Write

Patch action entering a safety gate that checks writable roots, asks the user, rejects, or runs through sandbox toward executor filesystem
Patch safety is path-aware. Codex compares the verified action with writable roots and approval policy before the executor filesystem writes.

The handler computes affected absolute paths and additional permissions before runtime execution. file_paths_for_action includes move destinations as well as source paths. write_permissions_for_paths derives read-write roots for paths that are not already writable under the current sandbox policy.

The safety decision itself lives in assess_patch_safety. Its output shape is intentionally small:

pub enum SafetyCheck {
    AutoApprove { ... },
    AskUser,
    Reject { reason: String },
}

This stage protects against two different shortcuts. The first shortcut is assuming that a patch is safe because it is structured. It is not; structured edits can still target dangerous paths. The second shortcut is assuming that sandboxing replaces approval. It does not; sandbox availability and approval policy both influence whether Codex can auto-approve, ask, or reject.

The source also calls out a hard-link concern: even when a patch appears constrained to writable paths, the runtime may still run it in a sandbox because paths could be hard links to files outside writable roots. That is a useful example of a local filesystem fact that cannot be solved by pretty diff rendering.

5. Runtime Writes Through the Owning Filesystem

Approval does not directly write files. The approved request is passed to ApplyPatchRuntime, whose request carries the verified action, affected paths, protocol changes, approval requirement, and additional permissions.

pub struct ApplyPatchRequest {
    pub action: ApplyPatchAction,
    pub file_paths: Vec<AbsolutePathBuf>,
    ...
}

Inside run, the runtime takes the primary turn environment, obtains its filesystem, builds a filesystem sandbox context for the current attempt, and calls the apply-patch library. That is why patch application works as a turn-owned operation. In a local turn, the filesystem can be local. In a remote turn, the environment can supply a remote filesystem. The patch protocol does not have to assume which one it is writing.

The apply library then uses the filesystem abstraction for actual reads, writes, directory creation, and removals. apply_hunks_to_files handles add, delete, update, and move hunks. It also updates an AppliedPatchDelta as work commits.

That explains why patch failure is not always empty. A move can write a destination and then fail to remove the source. A write can fail after truncation. The library therefore returns an ApplyPatchFailure with the delta that was definitely committed before the failure boundary. The runtime appends that committed delta and lets the event emitter finish with the evidence it has.

6. Diff Tracking Is Evidence, Not Decoration

Committed patch deltas entering a turn diff tracker, with exact evidence rendering a diff and uncertain evidence invalidating the display
The turn diff tracker is intentionally conservative. It renders when deltas are exact and invalidates when the evidence can no longer prove the net diff.

The AppliedPatchDelta model stores committed changes plus an exact flag. The TurnDiffTracker keeps baselines, current content, rename origins, and a validity bit.

pub struct TurnDiffTracker {
    valid: bool,
    baseline_by_path: HashMap<...>,
    current_by_path: HashMap<...>,
}

The tracker only accepts exact deltas. track_delta invalidates when a delta is not exact. get_unified_diff returns nothing when the tracker is invalid.

That is the final reason patch is a protocol. The output is not only “success” or “failure.” It is a structured mutation record with an honesty boundary. When evidence is exact, Codex can show a net diff. When evidence is not exact, the right behavior is to stop presenting a confident diff rather than reread unknown state and pretend the turn-level proof is still intact.

7. What This Design Buys

PressureSimpler designCodex patch designInvariant protected
Model emits shell heredocRun the shell commandDetect narrow forms and route to patch protocolMutation remains governed
Patch syntax parsesApply immediatelyVerify expected old content and paths firstNo best-effort edit from stale context
Path is outside writable rootsLet sandbox catch it lateAssess patch safety before runtimeApproval and policy stay visible
Local vs remote workspaceAssume local filesUse the turn environment filesystemEdits land in the owning workspace
Partial failureReport only failurePreserve committed delta and exactnessEvidence survives failure honestly
Diff UI expectedAlways show a diffRender only while tracker is validDiff is proof, not decoration

Apply This

  1. Make edit intent structured before mutation. A patchable action should name paths, operations, and expected old content.
  2. Treat compatibility syntax as an input adapter. Intercept shell-like forms only when they clearly map to the protocol, then normalize them immediately.
  3. Run safety before runtime. Approval policy, writable roots, and sandbox availability belong before the write, not after a failure.
  4. Write through the owner. The runtime should use the filesystem that owns the turn, whether local or remote.
  5. Preserve exact evidence and invalidate honestly. A diff that cannot be proven should disappear rather than become theater.

Chapter 12 moves outward from the patch lane to the human and automated gates around all side effects: hooks, approval requests, Guardian review, and client surfaces that can pause execution while a decision is pending.

Source Map

ConceptSource anchor
Patch handler struct and tool surfaceApplyPatchHandler, ToolHandler impl
Handler verification and orchestrationApplyPatchHandler::handle
Shell interception pathintercept_apply_patch
Patch grammar and hunk modelparser.rs
Invocation verifiermaybe_parse_apply_patch_verified
Patch safety assessmentassess_patch_safety
Patch runtimeApplyPatchRuntime::run
Hunk application and committed deltaapply_hunks_to_files
Turn diff trackerturn_diff_tracker.rs