can1357/oh-my-pi · error · ParseError
The first line of the patch must be '*** Begin Patch'
Error message
The first line of the patch must be '*** Begin Patch'
What it means
The apply-patch parser requires the literal marker '*** Begin Patch' as the first non-dropped line of the patch. In non-streaming mode a missing marker is a hard ParseError; in streaming mode it returns [] (waiting for more data). This guards against feeding arbitrary text into the patch format.
Source
Thrown at packages/coding-agent/src/edit/apply-patch/parser.ts:71
}
function parseApplyPatchWithOptions(patchText: string, options: ParseApplyPatchOptions): PatchInput[] {
const streaming = options.streaming === true;
let lines = patchText.trim().split("\n");
// Lenient heredoc strip: <<EOF / <<'EOF' / <<"EOF" ... EOF
if (lines.length >= 2) {
const first = lines[0];
const last = lines[lines.length - 1].trim();
const validOpeners = new Set(["<<EOF", "<<'EOF'", '<<"EOF"']);
if (validOpeners.has(first) && last === "EOF") {
lines = lines.slice(1, lines.length - 1);
}
}
if (lines.length === 0 || lines[0].trim() !== BEGIN_PATCH_MARKER) {
if (streaming) return [];
throw new ParseError("The first line of the patch must be '*** Begin Patch'");
}
const hasEndMarker = lines[lines.length - 1].trim() === END_PATCH_MARKER;
if (!hasEndMarker && !streaming) {
throw new ParseError("The last line of the patch must be '*** End Patch'");
}
const hunks: PatchInput[] = [];
let remaining = hasEndMarker ? lines.slice(1, lines.length - 1) : lines.slice(1);
// Line numbers are 1-based and include the `*** Begin Patch` line (= 1).
let lineNumber = 2;
while (remaining.length > 0) {
// Blank separator lines between hunks are ignored (spec §3.3).
if (remaining[0].trim() === "") {
remaining = remaining.slice(1);
lineNumber++;
continue;
}View on GitHub (pinned to 9690622007)
Solutions
- Ensure the patch text begins with the exact line '*** Begin Patch' (no fences, no leading prose)
- Strip markdown code fences and any preamble before the marker before parsing
- If the input might be a unified diff, route it to the unified-diff applier instead
- Catch ParseError and, for model-generated patches, re-request the patch in the correct format
Example fix
// before
await applyCodexPatch('```\n*** Begin Patch\n...\n```');
// after
const stripped = patchText.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '');
await applyCodexPatch(stripped.trimStart()); Defensive patterns
Strategy: try-catch
Validate before calling
if (!patchText.trimStart().startsWith('*** Begin Patch')) {
throw new SkipOperation('not a codex patch');
} Type guard
function isCodexPatch(text) { return text.trimStart().startsWith('*** Begin Patch'); } Try / catch
try {
await applyCodexPatch(patchText);
} catch (err) {
if (err instanceof ParseError && err.message.includes("'*** Begin Patch'")) {
patchText = stripFencesAndPreamble(patchText);
await applyCodexPatch(patchText);
}
} Prevention
- Strip markdown fences from model output
- Instruct models to output the patch with no surrounding prose
- Validate the first line before parsing
When it happens
Trigger: Calling parseApplyPatch (via applyCodexPatch) with text whose first line is not exactly '*** Begin Patch' (after trim) — e.g. a model prefixed the patch with prose or a code fence, or used lowercase/variant markers.
Common situations: LLM output wrapped the patch in ``` fences so the first line is '```'; leading explanation text before the marker; copied patch lost its first line; applying a unified-diff (--- a/...) instead of a codex patch.
Related errors
- Update file hunk for path '${path}' is empty
- '${firstLine}' is not a valid hunk header. Valid hunk header
- The last line of the patch must be '*** End Patch'
- No files were modified.
- patch does not apply: {message}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/44f5f5b247b251b5.
Report an issue: GitHub.