apply_patch-Protokoll
apply_patch ist das von codex verwendete Textpatch-Format zum Ändern von Dateien. Das Modell erzeugt in einem Tool-Call einen *** Begin Patch ... *** End Patch-Text, den der Client in Hunks zerlegt und auf dem Dateisystem des exec-server absetzt. Es ist laxer als Unified Diff: unterstützt Add/Delete/Update-Hunks, Update nutzt @@ context + +/-/ -Zeilen für Ersetzung und kann über *** Move to: auch rename. Die Logik steckt im Crate apply-patch/; core/src/apply_patch.rs ist auf core-Seite das Sicherheitsgate und die Protokollübersetzung.
Verantwortlichkeiten
- Patch-Text parsen:
parse_patchzerlegt den String in eineHunk-Enum (AddFile / DeleteFile / UpdateFile); UpdateFile enthält eine Liste vonUpdateFileChunk, jeder chunk mit change_context + old_lines + new_lines (codex-rs/apply-patch/src/parser.rs:130-137). - Aufrufform erkennen:
maybe_parse_apply_patchunterscheidet zwei argv-Formen — direktapply_patch <body>oder als Shell-Heredocbash -c "apply_patch <<'EOF' ... EOF"; letzteres benötigt tree-sitter bash, um das Heredoc zu extrahieren (codex-rs/apply-patch/src/invocation.rs:112-137). - Pfad und cwd validieren:
try_verify_apply_patch_argslöst den relativen Pfad jedes Hunks übereffective_cwdzu einemPathUriauf; das workdir stammt aus demcd <path> &&-Präfix vor dem Heredoc (codex-rs/apply-patch/src/invocation.rs:180-200). - Auf Dateisystem anwenden:
apply_patch/apply_hunksführt add/delete/update über dasExecutorFileSystem-Trait in der Sandbox aus und liefert einAppliedPatchDelta(codex-rs/apply-patch/src/lib.rs:276-347). - core-Seite Sicherheitsklassifikation:
core/src/apply_patch.rs::apply_patchübergibt die Action anassess_patch_safety, die inAutoApprove/AskUser/Rejectklassifiziert, und entscheidet dann über direkte Ausführung oder Runtime-Approval (codex-rs/core/src/apply_patch.rs:34-75).
Entwurfsbeweggründe
Warum nicht einfach Unified Diff? Zwei Gründe. Erstens verlangt der Hunk-Header von Unified Diff (@@ -a,b +c,d @@) vom Modell, Zeilennummern exakt zu berechnen — das kann das Modell nicht gut. apply_patch markiert die Position im Update per @@ context und erlaubt seek_sequence, in der Originaldatei nach der Kontextzeile zu suchen; Zeilennummerverschiebungen werden toleriert. Zweitens unterstützt Unified Diff nativ nicht „Datei neu anlegen" und „Datei löschen" innerhalb desselben Patches; apply_patch unterscheidet explizit über *** Add File: / *** Delete File:.
Warum zusätzlich eine core/src/apply_patch.rs-Schicht? Weil der apply-patch-Crate reine Dateioperationen ausführt und von Sandbox-Policy, Approval und exec_approval_requirement auf core-Seite nichts weiß. Diese Schicht übersetzt eine ApplyPatchAction in ein InternalApplyPatchInvocation: entweder Output (Ergebnis direkt, weil der Nutzer schon explizit zugestimmt hat) oder DelegateToRuntime (an den Orchestrator übergeben, der Sandbox oder Approval-Flow durchläuft).
Der Fehler ImplicitInvocation ist ein interessantes Design: Wenn das Modell den Patch-Text direkt als argv[0] übergibt (ohne das apply_patch-Kommando drumherum), führt maybe_parse_apply_patch_verified ihn nicht stillschweigend aus, sondern liefert einen ImplicitInvocation-Fehler mit dem Hinweis „muss als ["apply_patch", "<patch>"]-Form erfolgen". So wird vermieden, dass Patch-Inhalte versehentlich als reguläres Shell-Kommando ausgeführt werden.
Wichtige Dateien
codex-rs/apply-patch/src/parser.rs:64-109 — Hunk-Enum, Datenstruktur der drei Hunk-Typen; resolve_path löst über cwd auf.codex-rs/apply-patch/src/parser.rs:178-200 — parse_patch_text, Hauptkörper, unterstützt Strict/Lenient.codex-rs/apply-patch/src/streaming_parser.rs:22-46 — StreamingPatchParser, zeilenweiser Zustandsautomat, geeignet für große Patches.codex-rs/apply-patch/src/invocation.rs:36-52 — MaybeApplyPatch / ExtractHeredocError, erkennt Shell-verpackte Patches.codex-rs/apply-patch/src/invocation.rs:141-166 — maybe_parse_apply_patch_verified, lehnt erst nackte Patches ab, dann verify.codex-rs/apply-patch/src/lib.rs:276-312 — apply_patch, schreibt den Patch-Text auf das Dateisystem.codex-rs/apply-patch/src/lib.rs:361-389 — apply_hunks_to_files, hunkweise Ausführung; das Makro try_write! markiert bei Schreibfehler delta als nicht exact.codex-rs/apply-patch/src/lib.rs:675-711 — derive_new_contents_from_chunks, liest Originaldatei, berechnet Replacement, erzeugt neuen Inhalt.codex-rs/core/src/apply_patch.rs:34-75 — apply_patch auf core-Seite; nach Sicherheitsklassifikation Entscheidung des Ausführungspfads.Die Struktur der drei Hunk-Typen ist unkompliziert; UpdateFile trägt zusätzlich move_path für rename:
// parser.rs:64-82 — 三类 hunk,UpdateFile 支持多 chunk + rename
pub enum Hunk {
AddFile {
path: PathBuf,
contents: String,
},
DeleteFile {
path: PathBuf,
},
UpdateFile {
path: PathBuf,
move_path: Option<PathBuf>,
chunks: Vec<UpdateFileChunk>,
},
}Auf core-Seite ist die Sicherheitsbeurteilung der zentrale Verzweigungspunkt. assess_patch_safety kombiniert Approval-Policy, permission_profile und Sandbox-Policy und teilt den Patch in „automatisch freigeben", „Benutzer fragen" und „direkt ablehnen" ein. Bei Ablehnung wird die reason in FunctionCallError::RespondToModel eingepackt, damit das Modell erfährt, warum nichts geändert wurde:
// core/src/apply_patch.rs:39-74 — 三路分流,Output 表示不走 runtime
match assess_patch_safety(&action, ...) {
SafetyCheck::AutoApprove { user_explicitly_approved, .. } => {
InternalApplyPatchInvocation::DelegateToRuntime(ApplyPatchRuntimeInvocation {
action,
auto_approved: !user_explicitly_approved,
exec_approval_requirement: ExecApprovalRequirement::Skip { .. },
})
}
SafetyCheck::AskUser => InternalApplyPatchInvocation::DelegateToRuntime(..),
SafetyCheck::Reject { reason } => InternalApplyPatchInvocation::Output(Err(
FunctionCallError::RespondToModel(format!("patch rejected: {reason}")),
)),
}Schlägt das Schreiben fehl, markiert das Makro try_write! delta.exact als false — „die Änderung wurde notiert, ist aber möglicherweise unvollständig". Bei ENOSPC kann die Datei schon truncated, aber nicht fertig geschrieben sein; das Delta bleibt erhalten, weil ein Rollback den Ursprungszustand wissen muss:
// lib.rs:378-388 — 写失败时 delta 立即失真
macro_rules! try_write {
($result:expr) => {
match $result {
Ok(value) => value,
Err(error) => {
delta.exact = false;
return Err(anyhow::Error::from(error));
}
}
};
}Datenfluss
Grenzen und Fehler
- Lenient-Modus standardmäßig an:
PARSE_IN_STRICT_MODE = false, weil gpt-4.1 den Patch in<<'EOF' ... EOF-Heredoc verpackt als argv übergibt und der Strict-Modus alles ablehnen würde (codex-rs/apply-patch/src/parser.rs:47-53). - Nackter Patch wird abgelehnt: Wenn argv nur aus einem Element besteht, das als Patch parsebar ist, wird ein
ImplicitInvocation-Fehler zurückgegeben mit dem Hinweis, als["apply_patch", "<patch>"]neu zu laufen, damit Patch-Inhalte nicht versehentlich als Shell-Kommando ausgeführt werden (codex-rs/apply-patch/src/invocation.rs:147-158). - UpdateFile mit leeren chunks:
StreamingPatchParser::ensure_update_hunk_is_not_emptyprüft, dass ein Update-Hunk Inhalt hat; ein leerer Hunk liefert direktInvalidHunkError(codex-rs/apply-patch/src/streaming_parser.rs:53-82). - Schreibfehler, Delta aber erhalten: Bei Fehlern wie ENOSPC kann die Datei schon truncated, aber nicht fertig geschrieben sein;
delta.exact = falsemarkiert die Änderung als unsicher, für spätere Rollbacks oder Audits (codex-rs/apply-patch/src/lib.rs:375-388). - DeleteFile unterscheidet Verzeichnisse: Vor dem Löschen stellt
ensure_not_directorysicher, dass das Ziel eine Datei und kein Verzeichnis ist, um versehentliches Löschen von Verzeichnissen zu vermeiden (codex-rs/apply-patch/src/lib.rs:423-430).
Zusammenfassung
apply_patch ist das Standard-Protokoll von codex zum Ändern von Dateien: Der Parser zerlegt den Text in Hunks, die core-Seite klassifiziert die Sicherheit, und der exec-server übernimmt das eigentliche Schreiben. Die fehleranfälligsten Stellen der Kette sind Pfadauflösung und Heredoc-Erkennung — die Shell-Wrapper des Modells kommen in vielen Formen, und der Lenient-Modus existiert genau deshalb. Wie der Patch in der Sandbox durchläuft, siehe Sandbox und Befehlsisolierung; wie der Tool-Call an apply_patch dispatcht wird, siehe Werkzeugaufrufe und function_tool.