can1357/oh-my-pi · error · ParseError
The last line of the patch must be '*** End Patch'
Error message
The last line of the patch must be '*** End Patch'
What it means
The parser requires the literal '*** End Patch' marker as the final line of a complete patch. In non-streaming mode an unterminated patch is a ParseError; in streaming mode it is tolerated because more data may follow. This ensures the patch is complete before any file operations run.
Source
Thrown at packages/coding-agent/src/edit/apply-patch/parser.ts:75
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;
}
const firstLine = remaining[0].trim();
if (firstLine.startsWith(ADD_FILE_MARKER)) {View on GitHub (pinned to 9690622007)
Solutions
- Append the exact line '*** End Patch' as the last line of the patch
- Increase the model's max output tokens / re-generate if the patch was truncated
- Trim trailing junk so the last line is exactly the marker
- Use streaming parsing if you intentionally parse incomplete patches as they arrive
Example fix
// before
await applyCodexPatch('*** Begin Patch\n*** Update File: a.ts\n@@ ...'); // truncated
// after
await applyCodexPatch(patchText.trimEnd() + '\n*** End Patch'); Defensive patterns
Strategy: try-catch
Validate before calling
if (patchText.trimStart().startsWith('*** Begin Patch') && !patchText.trimEnd().endsWith('*** End Patch')) {
throw new SkipOperation('patch truncated: missing End Patch marker');
} Type guard
function isCompletePatch(text) {
const t = text.trim();
return t.startsWith('*** Begin Patch') && t.endsWith('*** End Patch');
} Try / catch
try {
await applyCodexPatch(patchText);
} catch (err) {
if (err instanceof ParseError && err.message.includes("'*** End Patch'")) {
patchText = patchText.trimEnd() + '\n*** End Patch';
await applyCodexPatch(patchText);
}
} Prevention
- Check for the End marker before applying
- Raise max output tokens to avoid truncation
- Use streaming parse mode when consuming partial output
When it happens
Trigger: Calling parseApplyPatch/applyCodexPatch with patch text that starts with '*** Begin Patch' but is truncated or missing the closing '*** End Patch' line (streaming=false).
Common situations: Model output cut off by a token limit; patch copied only partially; a trailing newline/extra text after the marker causing the last line check to fail; log capture that dropped the final line.
Related errors
- The first line of the patch must be '*** Begin Patch'
- Update file hunk for path '${path}' is empty
- '${firstLine}' is not a valid hunk header. Valid hunk header
- 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/a9572f951e2398f1.
Report an issue: GitHub.