openai/codex · error · ParseError

invalid patch: {0}

Error message

invalid patch: {0}

What it means

Raised by the apply-patch parser when the patch text does not conform to the grammar at the whole-patch level (start: begin_patch hunk+ end_patch). The {0} string carries the specific reason - typically a missing '*** Begin Patch' header or '*** End Patch' trailer. The parser runs lenient by default (PARSE_IN_STRICT_MODE = false, kept lenient for gpt-4.1 quirks), so this fires only on fundamentally broken input, not minor whitespace deviations; hunk-level problems use InvalidHunkError instead.

Source

Thrown at codex-rs/apply-patch/src/parser.rs:57

pub(crate) const ADD_FILE_MARKER: &str = "*** Add File: ";
pub(crate) const DELETE_FILE_MARKER: &str = "*** Delete File: ";
pub(crate) const UPDATE_FILE_MARKER: &str = "*** Update File: ";
pub(crate) const MOVE_TO_MARKER: &str = "*** Move to: ";
pub(crate) const EOF_MARKER: &str = "*** End of File";
pub(crate) const CHANGE_CONTEXT_MARKER: &str = "@@ ";
pub(crate) const EMPTY_CHANGE_CONTEXT_MARKER: &str = "@@";

/// Currently, the only OpenAI model that knowingly requires lenient parsing is
/// gpt-4.1. While we could try to require everyone to pass in a strictness
/// param when invoking apply_patch, it is a pain to thread it through all of
/// the call sites, so we resign ourselves allowing lenient parsing for all
/// models. See [`ParseMode::Lenient`] for details on the exceptions we make for
/// gpt-4.1.
const PARSE_IN_STRICT_MODE: bool = false;

#[derive(Debug, PartialEq, Error, Clone)]
pub enum ParseError {
    #[error("invalid patch: {0}")]
    InvalidPatchError(String),
    #[error("invalid hunk at line {line_number}, {message}")]
    InvalidHunkError { message: String, line_number: usize },
}
use ParseError::*;

#[derive(Debug, PartialEq, Clone)]
#[allow(clippy::enum_variant_names)]
pub enum Hunk {
    AddFile {
        path: PathBuf,
        contents: String,
    },
    DeleteFile {
        path: PathBuf,
    },
    UpdateFile {
        path: PathBuf,

View on GitHub (pinned to 339751715c)

Solutions

  1. Ensure the patch starts with '*** Begin Patch' and ends with '*** End Patch' on their own lines
  2. Strip surrounding markdown fences and prose before handing model output to apply_patch
  3. Check for truncation and regenerate the full patch
  4. Read the {0} detail - it names the exact structural problem

Example fix

// before
let patch = raw_model_output; // wrapped in ``` fences -> InvalidPatchError

// after
let patch = raw_model_output.trim()
    .trim_start_matches('`').trim_start_matches("diff").trim()
    .trim_end_matches('`').trim();
debug_assert!(patch.starts_with("*** Begin Patch") && patch.ends_with("*** End Patch"));
Defensive patterns

Strategy: validation

Validate before calling

fn looks_like_patch(s: &str) -> bool {
    s.starts_with("*** Begin Patch") && s.trim_end().ends_with("*** End Patch")
}

if !looks_like_patch(&model_output) {
    return Err("model did not emit a bare patch");
}

Type guard

fn is_invalid_patch(e: &ParseError) -> bool {
    matches!(e, ParseError::InvalidPatchError(_))
}

Try / catch

match apply_patch(patch, ...).await {
    Err(failure) => match failure.into_parts().0 {
        ApplyPatchError::ParseError(ParseError::InvalidPatchError(msg)) => {
            eprintln!("patch malformed: {msg}"); // log raw patch alongside
        }
        other => return Err(other.into()),
    },
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Passing text without the '*** Begin Patch'/'*** End Patch' markers to apply_patch; model output wrapped in markdown fences or prose; a response truncated before the end marker; hand-built patch strings missing the markers.

Common situations: LLM output that surrounds the patch with ``` fences or explanations; streaming responses cut mid-patch; encoding corruption of the first/last lines.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/cb04c16a90ed79ed. Report an issue: GitHub.