Skip to content

apply_patch-Protokoll

源码版本rust-v0.145.0

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

  1. Patch-Text parsen: parse_patch zerlegt den String in eine Hunk-Enum (AddFile / DeleteFile / UpdateFile); UpdateFile enthält eine Liste von UpdateFileChunk, jeder chunk mit change_context + old_lines + new_lines (codex-rs/apply-patch/src/parser.rs:130-137).
  2. Aufrufform erkennen: maybe_parse_apply_patch unterscheidet zwei argv-Formen — direkt apply_patch <body> oder als Shell-Heredoc bash -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).
  3. Pfad und cwd validieren: try_verify_apply_patch_args löst den relativen Pfad jedes Hunks über effective_cwd zu einem PathUri auf; das workdir stammt aus dem cd <path> &&-Präfix vor dem Heredoc (codex-rs/apply-patch/src/invocation.rs:180-200).
  4. Auf Dateisystem anwenden: apply_patch / apply_hunks führt add/delete/update über das ExecutorFileSystem-Trait in der Sandbox aus und liefert ein AppliedPatchDelta (codex-rs/apply-patch/src/lib.rs:276-347).
  5. core-Seite Sicherheitsklassifikation: core/src/apply_patch.rs::apply_patch übergibt die Action an assess_patch_safety, die in AutoApprove / AskUser / Reject klassifiziert, 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-109Hunk-Enum, Datenstruktur der drei Hunk-Typen; resolve_path löst über cwd auf.codex-rs/apply-patch/src/parser.rs:178-200parse_patch_text, Hauptkörper, unterstützt Strict/Lenient.codex-rs/apply-patch/src/streaming_parser.rs:22-46StreamingPatchParser, zeilenweiser Zustandsautomat, geeignet für große Patches.codex-rs/apply-patch/src/invocation.rs:36-52MaybeApplyPatch / ExtractHeredocError, erkennt Shell-verpackte Patches.codex-rs/apply-patch/src/invocation.rs:141-166maybe_parse_apply_patch_verified, lehnt erst nackte Patches ab, dann verify.codex-rs/apply-patch/src/lib.rs:276-312apply_patch, schreibt den Patch-Text auf das Dateisystem.codex-rs/apply-patch/src/lib.rs:361-389apply_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-711derive_new_contents_from_chunks, liest Originaldatei, berechnet Replacement, erzeugt neuen Inhalt.codex-rs/core/src/apply_patch.rs:34-75apply_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:

rust
// 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:

rust
// 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:

rust
// 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_empty prüft, dass ein Update-Hunk Inhalt hat; ein leerer Hunk liefert direkt InvalidHunkError (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 = false markiert 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_directory sicher, 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.